如何表示部分可为空的外键?

How to represent partially nullable foreign keys?

假设您要在数据库中存储联系人 phone 号码、人员和家庭。每个人都属于一个家庭。 phone 号码可能与家庭中的特定个人相关联,也可能是家庭的通用号码。这些关系在以下 Oracle SQL 中部分表达:

CREATE TABLE HOUSEHOLD (
    HOUSEHOLD_ID INTEGER PRIMARY KEY
);

CREATE TABLE PERSON (
    PERSON_ID INTEGER PRIMARY KEY,
    HOUSEHOLD_ID INTEGER NOT NULL,
    CONSTRAINT FK_PERSON_HOUSEHOLD
        FOREIGN KEY (HOUSEHOLD_ID)
        REFERENCES HOUSEHOLD (HOUSEHOLD_ID)
);

CREATE TABLE CONTACT_PHONE (
    PHONE_NUMBER CHAR(10) PRIMARY KEY,
    HOUSEHOLD_ID INTEGER NOT NULL,
    PERSON_ID INTEGER NULL,
    CONSTRAINT FK_PHONE_HOUSEHOLD
        FOREIGN KEY (HOUSEHOLD_ID)
        REFERENCES HOUSEHOLD (HOUSEHOLD_ID),
    CONSTRAINT FK_PHONE_PERSON
        FOREIGN KEY (PERSON_ID)
        REFERENCES PERSON (PERSON_ID)
);

外键和 NULL/NOT NULL 约束确保每个人都属于一个家庭,每个联系人 phone 都与一个家庭关联,并且联系人 phone可能与人有关,也可能不与人有关。他们没有阻止的一件事是 phone 号码与一个家庭相关联,并且与属于不同家庭的人相关联。有没有一种标准的方法来使用数据库约束来表达这种关系?给出的示例适用于 Oracle,但也欢迎使用适用于其他数据库平台的解决方案。

我们想要一个指向 PERSON table 的 HOUSEHOLD_ID 和 PERSON_ID 列的外键,但如果 PERSON_ID CONTACT_PHONE table 的列为 NULL。解决方案是创建一个复制HOUSEHOLD_ID的virtual/computed列,但仅当PERSON_ID不为NULL时,然后在外键中使用它而不是HOUSEHOLD_ID:

CREATE TABLE CONTACT_PHONE (
    PHONE_NUMBER CHAR(10) PRIMARY KEY,
    HOUSEHOLD_ID INTEGER NOT NULL,
    PERSON_ID INTEGER NULL,
    PERSON_HOUSEHOLD_ID GENERATED ALWAYS AS (
        CAST(DECODE(PERSON_ID, NULL, NULL, HOUSEHOLD_ID) AS INTEGER)
    ) VIRTUAL,
    CONSTRAINT FK_PHONE_HOUSEHOLD
        FOREIGN KEY (HOUSEHOLD_ID)
        REFERENCES HOUSEHOLD (HOUSEHOLD_ID),
    CONSTRAINT FK_PHONE_PERSON
        FOREIGN KEY (PERSON_HOUSEHOLD_ID, PERSON_ID)
        REFERENCES PERSON (HOUSEHOLD_ID, PERSON_ID)
);

这样,当PERSON_ID不为NULL时,PERSON_HOUSEHOLD_ID会和HOUSEHOLD_ID一样,正常检查FK_PHONE_PERSON

但是,当 PERSON_ID 为 NULL 时,PERSON_HOUSEHOLD_ID 也将为 NULL。由于参与 FK_PHONE_PERSON 的两个本地列都是 NULL,因此不会检查约束。