涉及多个表的外键约束

Rei*_*ica 5 postgresql database-design referential-integrity constraints foreign-keys

我在 Postgres 9.3 数据库中有以下场景:

  • 表 B 和 C 参考表 A。
  • 表 C 有一个引用表 B 的可选字段。

我想确保对于引用表 B 的表 C 的每一行,cba = ca 也就是说,如果 C 引用了 B,那么两行都应该指向表 A 中的同一行。

  • 我可以重构表 C,以便如果指定了 cb,则 ca 为空,但这会使连接表 A 和 C 的查询变得笨拙。
  • 我也许还可以让表 B 的主键包括它对表 A 的引用,然后让表 C 对表 B 的外键包括表 C 对表 A 的引用,但我认为这种调整太笨拙而无法证明好处的合理性。
  • 我认为这可以通过在表 C 上插入/更新之前运行并拒绝违反指定约束的操作的触发器来完成。

在这种情况下,是否有更好的方法来强制执行数据完整性?

Erw*_*ter 5

有一个非常简单、万无一失的解决方案。适用于 Postgres 9.3 - 当提出原始问题时。适用于当前的 Postgres 13 - 当添加赏金中的问题时:

\n
\n

希望了解是否可以在没有数据库触发器的情况下实现这一点

\n
\n

FOREIGN KEY约束可以跨越多个列。只需将表 A 的 ID 包含在从表 C 到表 B 的 FK 约束中即可。这会强制 B 和 C 中的链接行始终指向 A 中的同一行。例如:

\n
CREATE TABLE a (\n  a_id int PRIMARY KEY\n);\n\nCREATE TABLE b (\n  b_id int PRIMARY KEY\n, a_id int NOT NULL REFERENCES a\n, UNIQUE (a_id, b_id)  -- redundant, but required for FK\n);\n\nCREATE TABLE c (\n  c_id int PRIMARY KEY\n, a_id int NOT NULL REFERENCES a\n, b_id int\n, CONSTRAINT fk_simple_and_safe_solution\n  FOREIGN KEY (a_id, b_id) REFERENCES b(a_id, b_id)  -- THIS !\n);\n
Run Code Online (Sandbox Code Playgroud)\n

最小样本数据:

\n
INSERT INTO a(a_id) VALUES\n  (1)\n, (2);\n\nINSERT INTO b(b_id, a_id) VALUES\n  (1, 1)\n, (2, 2);\n\nINSERT INTO c(c_id, a_id, b_id) VALUES\n  (1, 1, NULL)  -- allowed\n, (2, 2, 2);    -- allowed\n
Run Code Online (Sandbox Code Playgroud)\n

按要求不允许:

\n
INSERT INTO c(c_id, a_id, b_id) VALUES (3,2,1);\n
Run Code Online (Sandbox Code Playgroud)\n
\n
ERROR:  insert or update on table "c" violates foreign key constraint "fk_simple_and_safe_solution"\nDETAIL:  Key (a_id, b_id)=(2, 1) is not present in table "b".\n
Run Code Online (Sandbox Code Playgroud)\n
\n

db<>在这里摆弄

\n

FK 约束的默认MATCH SIMPLE行为是这样的(引用手册):

\n
\n

MATCH SIMPLE允许任何外键列为空;如果其中任何一个为空,则该行不需要在引用的表中具有匹配项。

\n
\n

c(b_id)因此仍然允许NULL 值(根据要求: “可选字段”)。对于这种特殊情况,FK 约束被“禁用”。

\n

我们需要逻辑冗余UNIQUE约束b(a_id, b_id)以允许 FK 引用它。但是,通过将其设置为 on(a_id, b_id)而不是(b_id, a_id),它本身也很有用,可以提供有用的索引 onb(a_id)来支持其他 FK 约束等。看:

\n\n

(相应地,附加索引c(a_id)通常很有用。)

\n

进一步阅读:

\n\n