如何强制执行“每组一个”约束?

kjo*_*kjo 6 postgresql sqlite database-design constraint

我有一个x这样定义的表:

CREATE TABLE x (
    xid INTEGER NOT NULL PRIMARY KEY,
    yid INTEGER NOT NULL REFERENCES y(yid),
    is_principal BOOLEAN NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

该定义遗漏了一个必须满足的约束x。用英语来说,这个约束可以这样描述:

字段中可能有一行或多行具有给定值yid,但其中必须始终有一行is_principal字段为TRUE1

我正在寻找一种方法来强制执行此约束。

(如果重要的话,我对适用于 SQLite3 和 PostgreSQL 的解决方案特别感兴趣。)

编辑:为了清楚起见,上面的描述并不排除 table 中存在y其值yid在 table 中根本没有提及的行x。对于 的此类值,yid根本没有任何价值xid,无论是本金价值还是其他价值。仅对于yid 表中出现的 x那些值,表中必须有且只有一行x具有is_principal = TRUE


1表达相同约束的另一种方法是以下两个查询应始终产生相同的输出:

SELECT DISTINCT yid FROM x                    ORDER BY yid;
SELECT          yid FROM x WHERE is_principal ORDER BY yid;
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 3

问题与此类似:如何与特权孩子建立一对多关系?

约束的“每组最多一个”部分可以通过部分索引来解决:

CREATE UNIQUE INDEX is_FavoriteChild
  ON x (yid)
  WHERE is_principal  ;
Run Code Online (Sandbox Code Playgroud)

解决问题的另一种方法是删除该is_principal列并添加第三个表。这也不能解决“完全一样”的问题:

CREATE TABLE x (
    xid INTEGER NOT NULL PRIMARY KEY,
    yid INTEGER NOT NULL REFERENCES y (yid),
    UNIQUE (yid, xid)
);

CREATE TABLE x_principal (
    xid INTEGER NOT NULL,
    yid INTEGER NOT NULL PRIMARY KEY,
    FOREIGN KEY (yid, xid) REFERENCES x (yid, xid) 
);
Run Code Online (Sandbox Code Playgroud)

如果您想单独使用 DDL 强制执行“恰好一个”限制,可以在具有可延迟约束的 Postgres 中完成(我不认为这是 SQLite 中的一个选项)。

有关更多详细信息和选项,您可以在@Erwin的SO问题中看到出色的答案:SQLAlchemy中的复杂外键约束

(编辑答案以获取附加详细信息,即并非 的所有值都y.yid必须出现在 表 中x。添加了一个表):

--- This table will hold all values of yid that appear in table x
CREATE TABLE y_x (
    yid INTEGER NOT NULL PRIMARY KEY REFERENCES y (yid),
    --- **no other columns**
    principal_xid INTEGER NOT NULL
);

CREATE TABLE x (
    xid INTEGER NOT NULL PRIMARY KEY,
    yid INTEGER NOT NULL REFERENCES y_x (yid)
                         DEFERRABLE INITIALLY DEFERRED,
    UNIQUE (yid, xid)
);

ALTER TABLE y_x
ADD CONSTRAINT y_principal_x_fk
   FOREIGN KEY (yid, principal_xid)
   REFERENCES x (yid, xid)
   DEFERRABLE INITIALLY DEFERRED; 
Run Code Online (Sandbox Code Playgroud)