仅当第三列为 NOT NULL 时才使用两列外键约束

Jim*_*art 6 postgresql normalization foreign-key database-design constraint

鉴于以下表格:

CREATE TABLE verified_name (
  id               SERIAL PRIMARY KEY,
  name             TEXT NOT NULL,
  email            TEXT NOT NULL,
  UNIQUE (name, email)
);

CREATE TABLE address (
  id               SERIAL PRIMARY KEY,
  name             TEXT NOT NULL,
  email            TEXT NOT NULL,
  verified_name_id INTEGER NULL REFERENCES verified_name(id)
);
Run Code Online (Sandbox Code Playgroud)

如何添加附加约束,当address.verified_name_id不为 NULL 时,nameemail字段address必须与引用的字段匹配verified_name

我尝试将以下内容添加到address

FOREIGN KEY (name, email) REFERENCES verified_name(name, email)
Run Code Online (Sandbox Code Playgroud)

...但即使verified_name_id是 NULL ,该约束也会被应用。

我正在寻找类似于部分索引语法的东西,带有像这样的子句WHERE verified_name_id IS NOT NULL,但是简单地将这样的子句附加到FOREIGN KEY约束中是行不通的。

当前不受欢迎的解决方案:

我可以将以下约束添加到verified_name

UNIQUE (name, email),
UNIQUE (id, name, email)
Run Code Online (Sandbox Code Playgroud)

以及以下约束address

FOREIGN KEY (verified_name_id, name, email) REFERENCES verified_name(id, name, email)
Run Code Online (Sandbox Code Playgroud)

...但这会产生一个额外的约束verified_name,我不想有(这是一个有效的逻辑约束,但它也是多余的,并且有轻微的性能影响)。

Erw*_*ter 13

正确的解决方案

问题的核心是数据模型。在规范化模式中,您不会冗余地存储nameemail。看起来像这样:

CREATE TABLE name (
  name_id          SERIAL PRIMARY KEY,
  name             TEXT NOT NULL,
  email            TEXT NOT NULL,
  verified         BOOLEAN NOT NULL DEFAULT FALSE,
  UNIQUE (name, email)
);

CREATE TABLE address (
  address_id       SERIAL PRIMARY KEY,
  name_id          INT REFERENCES name(name_id)
  ...
);
Run Code Online (Sandbox Code Playgroud)

如果应该允许尚未验证的名称打破 UNIQUE 约束,您可以将其替换为部分 UNIQUE INDEX(就像您想到的那样):

CREATE UNIQUE INDEX name_verified_idx ON name(name, email) WHERE verified;
Run Code Online (Sandbox Code Playgroud)

用你拥有的东西工作

虽然坚持您不幸的设计,但您已经发现自己的解决方案完全符合您的要求。一个FOREIGN KEY默认的MATCH SIMPLE行为匹配

带有像WHERE verified_name_id IS NOT NULL.

引用手册

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

一个(不太可靠和更昂贵的)替代方法是在 INSERT / UPDATE in 上的address触发器和在 INSERT / UPDATE / DELETE in 上的触发器verified_name