如何将Postgres外键模仿到分区表中

时间:2015-03-03 01:20:36

标签: postgresql foreign-keys postgresql-9.3 database-partitioning

我有一个分区表(称之为A),其中一个串行主键由另一个表引用(称之为B)。我知道我不能实际从一个到另一个创建一个外键(因为我不知道数据实际存储在哪个分区),所以相反,我试图模仿使用检查约束的外键行为。如下所示:

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”关键字添加到我所在的行添加约束,但Postgres不支持可延迟检查约束。

我建议的方法是将我的检查约束转换为约束触发器,我可以推迟,然后在事务中导入我的数据库转储。对此有更直接的方法吗?例如,有没有办法让我告诉Postgres不要转储表B,直到表A及其所有分区都被转储(例如,从B添加依赖关系到A的分区)?我应该使用的其他一些模式呢?谢谢。

2 个答案:

答案 0 :(得分: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-list:

恢复转储
pg_restore -d your_database --use-list backup.txt database.dump

我使用您的示例测试了此过程并更改了表A和B的顺序。如果首先还原表A,则会恢复转储而不会出现错误。否则,如果首先恢复B,则恢复将按预期失败,并显示错误:

  

pg_restore:[archiver(db)]表“b”的COPY失败:错误:新行   对于关系“b”违反检查约束“b_akey_check”DETAIL:   失败的行包含(1)。背景:复制b,第1行:“1”警告:   恢复时忽略错误:1

答案 1 :(得分:0)

@TommasoDiBucchianico给出的两个选项都是有效的方法,但由于以下陷阱,我仍然想要一些不同的东西:

  

选项#1:重命名表格,使其按字母顺序排列   它们与加载表的顺序相匹配。

这个被避免了,因为1)它依赖于pg_dump的未记录的特征,2)它迫使我给每个表提供不太理想的名称。

  

选项#2:提供包含订单中表格的文本文件   我想让它们被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;