复合 FK 引用原子 PK + 非唯一属性

Posted

技术标签:

【中文标题】复合 FK 引用原子 PK + 非唯一属性【英文标题】:Composite FK referencing atomic PK + non unique attribute 【发布时间】:2021-10-09 09:33:58 【问题描述】:

我正在尝试在 Postgres 13.3 中创建以下表格:

CREATE TABLE IF NOT EXISTS accounts (
    account_id Integer PRIMARY KEY NOT NULL
);

CREATE TABLE IF NOT EXISTS users (
    user_id Integer PRIMARY KEY NOT NULL,
    account_id Integer NOT NULL REFERENCES accounts(account_id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS calendars (
    calendar_id Integer PRIMARY KEY NOT NULL,
    user_id Integer NOT NULL,
    account_id Integer NOT NULL,
    FOREIGN KEY (user_id, account_id) REFERENCES users(user_id, account_id) ON DELETE CASCADE
);

但创建日历表时出现以下错误:

ERROR:  there is no unique constraint matching given keys for referenced table "users"

这对我来说没有多大意义,因为外键包含 user_id,它是 users 表的 PK,因此也具有唯一性约束。如果我像这样在组合的 user_id 和 account_id 上添加显式唯一性约束:

ALTER TABLE users ADD UNIQUE (user_id, account_id);

然后我就可以创建日历表了。这个唯一约束对我来说似乎没有必要,因为 user_id 已经是唯一的。有人可以向我解释一下我在这里缺少什么吗?

【问题讨论】:

有什么特殊原因要使用原子 PK 为表创建复合 FK 吗? account_id 不是 users 上的唯一键。如果您想要一个 n-n 关系,请考虑一个中间表。 日历表的含义/意图是什么? 我使用复合 FK 的原因是因为我想确保 calendars 表中的 account_id 与 user_id 日历属性引用的用户的 account_id 相同。我的代码中有一个错误,其中日历上的 account_id 是用户不是其成员的帐户,因此我想使用架构强制执行此操作。 @wildplasser 日历是我的架构的一部分,用于对日历事件进行分组(我没有在此处添加)。一个用户可以拥有多个日历,但一个日历只能由一个用户拥有。所以这是一个 1-n 关系 【参考方案1】:

Postgres 非常聪明/愚蠢,它不会假设设计者会做愚蠢的事情。

Postgres 设计者可以采取不同的策略:

检测传递性,使FK不仅依赖users.id,还依赖users.account_id -> accounts.id。这是可行的,但代价高昂。它还涉及在目录中为单个 FK 约束添加多个依赖记录。施加约束时(在两个引用表中的任何一个中执行 UPDATE 或 DELETE),它可能会变得非常复杂。 检测传递性,并默默地忽略冗余列引用。这意味着:对程序员撒谎。它还需要在目录中显示。 级联 DDL 操作也会变得更加复杂。 (请记住:DDL 在并发/版本控制方面已经非常困难)

从执行/性能的角度来看:施加约束目前涉及对引用表的索引的“伪触发器”。 (DEFERRED除外,需要特殊处理)

所以,恕我直言,Postgres 开发人员做出了拒绝做愚蠢复杂事情的明智选择。

【讨论】:

感谢您的详细解释。我很感激。

以上是关于复合 FK 引用原子 PK + 非唯一属性的主要内容,如果未能解决你的问题,请参考以下文章

外键(FK_ 必须与引用的主键具有相同的列数

复合键实体并且不想声明PK键

数据库范式

在表中插入数据,在一个查询中使用 FK 更新其他表

查询联结表中FK引用的表中的其他字段

mysql复合外键引用超过2个属性