3

我有一個分區表(稱之爲A)和一個由另一個表(稱爲B)引用的串行主鍵。我知道我不能實際上創建一個外鍵從一個到另一個(因爲我不知道從哪個分區數據實際存儲),所以相反,我試圖模仿外鍵的行爲使用檢查約束。類似如下:如何將Postgres外鍵模仿到分區表中

CREATE TABLE A (
    MyKey SERIAL PRIMARY KEY 
); 

CREATE TABLE B (
    AKey INT, -- Should have: REFERENCES A (MyKey), 
       -- but can't due to Postgres limitations 
); 

CREATE TABLE APart1 (
    Field1 INT, 
    PRIMARY KEY (MyKey) 
) INHERITS (A); 

CREATE TABLE APart2 (
    Field2 INT, 
    PRIMARY KEY (MyKey) 
) INHERITS (A); 

CREATE FUNCTION ValidateKeyInA(aKey INT) RETURNS BOOL AS $$ 
    BEGIN 
     PERFORM * FROM A WHERE MyKey = aKey; 
     IF FOUND THEN 
      RETURN TRUE; 
     END IF; 
     RETURN FALSE; 
    END; 
$$ LANGUAGE PLPGSQL; 

ALTER TABLE B ADD CHECK (ValidateKeyInA(AKey)); 

WITH aKey AS (INSERT INTO APart1 (Field1) VALUES (1) RETURNING MyKey) 
INSERT INTO B (AKey) SELECT * FROM aKey; 

WITH aKey AS (INSERT INTO APart2 (Field2) VALUES (2) RETURNING MyKey) 
INSERT INTO B (AKey) SELECT * FROM aKey; 

這工作得很好,直到我去轉儲和恢復數據庫。此時,Postgres不知道表B依賴於表A中的數據(及其分區),而B恰好在表A之前被轉儲。我嘗試將「DEFERRABLE」關鍵字添加到行I中添加約束,但Postgres不支持可延遲檢查約束。

我建議的方法是將我的檢查約束轉換爲約束觸發器,我可以推遲,然後在事務中導入我的數據庫轉儲。有沒有更直接的方法呢?例如,是否有某種方式讓我告訴Postgres不要轉儲表B,直到表A及其所有分區都被轉儲(例如,添加從B到A分區的依賴關係)?我應該使用其他一些模式嗎?謝謝。

+2

我不明白你是如何實現表分區的。主表是哪一張?爲什麼你不使用繼承? Btw你的檢查函數可以更簡單:'CREATE FUNCTION ValidateKeyInA(aKey INT)RETURNS BOOL AS $$ SELECT count(*)> 0 FROM A WHERE Key = aKey; $$ LANGUAGE sql;' – 2015-03-03 07:22:53

+0

@TommasoDiBucchianico我編輯了我的問題,明確顯示瞭如何創建A的分區。我不知道在PLPGSQL函數中編寫單個select語句會隱式返回結果。感謝您的建議(幾乎每次訪問SO頁面時我都會學到一些新東西)。 – 2015-03-03 12:51:54

+0

只有sql函數隱式返回查詢的結果。我的函數是用sql語言編寫的,而不是plpgsql – 2015-03-03 12:59:40

回答

0

兩個由@TommasoDiBucchianico給出的選項是有效的方法,但我還是想要的東西不同,由於以下缺陷:

Option #1: Rename the tables such that the alphabetical ordering of them matches the order in which to load the tables.

這一個是被避免的,因爲1)它依賴於pg_dump的一個未公開的特性,2)它迫使我給每個表提供不太理想的名字。

Option #2: Provide a text file that contains the tables in the order in which I want them to be loaded by pg_restore.

我真的很喜歡這個選項,但不足之處是,任何時候的表重命名,添加或刪除,有人將不得不手動修改文本文件,以重新定義排序。

而不是嘗試重新排序數據,我決定改爲將所有違規檢查約束轉換爲約束觸發器。雖然檢查約束是預數據,但約束觸發器是後數據。這意味着約束觸發器不會被加入,直到加載所有數據爲止,這不需要任何特定順序的數據。下面顯示了我如何修改原始帖子中的示例以使用約束觸發器:

CREATE TABLE A (
    MyKey SERIAL PRIMARY KEY 
); 

CREATE TABLE B (
    AKey INT 
); 

CREATE TABLE APart1 (
    Field1 INT, 
    PRIMARY KEY (MyKey) 
) INHERITS (A); 

CREATE TABLE APart2 (
    Field2 INT, 
    PRIMARY KEY (MyKey) 
) INHERITS (A); 

CREATE FUNCTION ValidateKeyInA() RETURNS TRIGGER AS $$ 
    BEGIN 
     PERFORM * FROM A WHERE MyKey = NEW.AKey; 
     IF NOT FOUND THEN 
      RAISE EXCEPTION '%: AKey not found in A', TG_NAME; 
     END IF; 
     RETURN NEW; 
    END; 
$$ LANGUAGE PLPGSQL; 

CREATE CONSTRAINT TRIGGER "ValidateTableB" 
    AFTER INSERT OR UPDATE ON B FROM A 
    FOR EACH ROW EXECUTE PROCEDURE ValidateKeyInA(); 

WITH aKey AS (INSERT INTO APart1 (Field1) VALUES (1) RETURNING MyKey) 
INSERT INTO B (AKey) SELECT * FROM aKey; 

WITH aKey AS (INSERT INTO APart2 (Field2) VALUES (2) RETURNING MyKey) 
INSERT INTO B (AKey) SELECT * FROM aKey; 
1

pg_dump按字母順序自動排序表(請參閱我上面的註釋)。但是,如果要更改表格的轉儲和恢復方式,但無法根據所需的順序重命名錶,則可以將--use-list選項與pg_restore一起使用。請參閱http://www.postgresql.org/docs/9.3/static/app-pgrestore.html

pg_restore允許控制順序,數據庫元素如何使用--use-list選項進行恢復。

你必須先使用選項-Fc傾倒在自定義格式的數據庫,否則你無法和pg_restore恢復轉儲:

pg_dump -Fc your_database -f database.dump 

比你生成它列出了轉儲所有元素文件:

pg_restore --list database.dump > backup.txt 

文件backup.txt將用作pg_restore選項--use-list的輸入,但首先您可以編輯該文件並使用複製/粘貼來更改行的順序。您可以獨立更改表格創建和數據插入。請注意您的名單保持一致。您也可以完全刪除行,以便從還原中排除元素。

最後用--use列表選項恢復轉儲:

pg_restore -d your_database --use-list backup.txt database.dump 

我測試這個程序你的榜樣,改變了表A和B的順序如果首先恢復表A,轉儲無錯地恢復。否則,如果B先恢復,恢復與錯誤預期失敗:

pg_restore: [archiver (db)] COPY failed for table "b": ERROR: new row for relation "b" violates check constraint "b_akey_check" DETAIL: Failing row contains (1). CONTEXT: COPY b, line 1: "1" WARNING: errors ignored on restore: 1