【问题标题】:How to Mimic Postgres Foreign Keys into a Partitioned Table如何将 Postgres 外键模拟到分区表中
【发布时间】:2015-05-03 13:17:56
【问题描述】:

我有一个分区表(称为 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 在表 A 及其所有分区都被转储之前不要转储表 B(例如,将 B 的依赖项添加到 A 的分区)?我应该改用其他一些模式?谢谢。

【问题讨论】:

  • 我不明白你是如何实现表分区的。哪一张是主桌?为什么不使用继承?顺便说一句,您的检查功能可能会更简单:CREATE FUNCTION ValidateKeyInA(aKey INT) RETURNS BOOL AS $$ SELECT count(*) > 0 FROM A WHERE Key = aKey; $$ LANGUAGE sql;
  • @TommasoDiBucchianico 我编辑了我的问题以明确显示我如何创建 A 的分区。我不知道在 PLPGSQL 函数中编写单个 select 语句会隐式返回结果。感谢您的建议(我几乎每次访问 SO 页面时都会学到一些新东西)。
  • 只有 sql 函数隐式返回查询结果。我的函数是 sql 语言,不是 plpgsql
  • pg_dump 按字母顺序转储表,因此实际上表 A 在表 B 之前转储。在您的示例中,转储中的顺序应为:A、Apart1、Apart2、B。您能命名您的表吗以便字母顺序与“功能”顺序相对应?
  • @TommasoDiBucchianico 你能引用你的来源吗?我找到了this thread,这表明不仅顺序不是字母顺序,而且实际上是不确定的(参考 Postgres v8.4.2)。我查看了 9.3 here 的 pg_dump 文档,也找不到保证任何顺序的地方。

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


【解决方案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”的复制失败:错误:新行 对于关系“b”违反检查约束“b_akey_check”细节: 失败行包含 (1)。上下文:副本 b,第 1 行:“1”警告: 还原时忽略的错误:1

【讨论】:

    【解决方案2】:

    @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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-09
      • 2017-07-29
      • 2020-05-27
      • 1970-01-01
      • 1970-01-01
      • 2022-11-23
      • 1970-01-01
      • 2021-11-03
      相关资源
      最近更新 更多