如何将 Postgres 外键模拟到分区 Table

How to Mimic Postgres Foreign Keys into a Partitioned Table

我有一个分区的 table(称为 A),其中有一个串行主键被另一个 table(称为 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 不知道 table B 依赖于 table A(及其分区)中的数据,而 B 恰好在 table A 之前被转储。我试过了将 "DEFERRABLE" 关键字添加到我添加约束的行,但 Postgres 不支持可延迟检查约束。

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

pg_dump 按字母顺序自动对 table 进行排序(请参阅我上面的评论)。但是,如果您想更改 table 的转储和恢复顺序,但无法根据所需顺序重命名 table,您可以使用 --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 的输入,但首先您可以编辑文件并使用 [=29= 更改行的顺序].您可以独立更改 table 创建和数据插入。请注意您的列表保持一致。您还可以完全删除行,以便从恢复中排除元素。

最后使用选项 --use-list:

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

我用你的例子测试了这个过程,并改变了 tables A 和 B 的顺序。如果 table 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

@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) 它迫使我为每个 table 提供不太理想的名称。

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

我真的很喜欢这个选项,但缺点是任何时候 table 被重命名、添加或删除,都必须手动修改文本文件以重新定义排序。

我没有尝试重新排序数据,而是决定将所有有问题的检查约束转换为约束触发器。虽然检查约束是预数据,但约束触发器是 post-数据。这意味着在加载所有数据之前不会添加约束触发器,这不需要任何特定顺序的数据就可以工作。下面展示我是如何修改原来post中的例子来使用约束触发器的:

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;