You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

多列外键为何允许包含NULL的不存在组合值?

多列外键中部分字段设为NULL时未触发约束的原因分析

这是个非常典型的复合外键认知误区,我来给你拆解背后的逻辑:

首先先还原你的操作场景:

CREATE TABLE Foo (
    FooId1 int NOT NULL,
    FooId2 int NOT NULL,
    PRIMARY KEY(FooId1, FooId2)
);
INSERT INTO Foo(FooId1, FooId2) VALUES (1, 10), (2, 20);

CREATE TABLE Bar (
    BarId int NOT NULL PRIMARY KEY,
    FooId1 int NULL,
    FooId2 int NULL
);
ALTER TABLE Bar ADD CONSTRAINT FK_FOO FOREIGN KEY(FooId1, FooId2) REFERENCES Foo(FooId1, FooId2);
INSERT INTO Bar(BarId) VALUES(42);

当执行UPDATE Bar SET FooId1 = 3, FooId2 = 30 WHERE BarId = 42;时,因为两个外键列都是非NULL的确定值,数据库会严格校验这个组合是否存在于父表Foo中,不存在就触发外键约束报错——这完全符合你的预期。

但执行UPDATE Bar SET FooId1 = NULL, FooId2 = 30 WHERE BarId = 42;时没有报错,核心原因是SQL标准对复合外键的校验规则

  • 只有当复合外键的所有列都不为NULL时,数据库才会强制检查该组合是否存在于父表中;
  • 只要复合外键中有任意一列是NULL,数据库就会跳过这个约束检查。

为什么会这样?因为NULL在SQL中代表「未知值」,数据库无法确认(NULL, 30)这个组合是否真的不存在于父表(毕竟NULL在逻辑上不等于任何值,包括它自己),所以会默认允许这种“不完整的引用”。

如果你希望避免这种部分NULL的情况,可以通过两种方式处理:

  • 直接将Bar表中的FooId1FooId2设置为NOT NULL,强制外键必须是完整的非NULL组合;
  • 添加CHECK约束,确保外键要么全为NULL,要么全为非NULL:
    ALTER TABLE Bar ADD CONSTRAINT CHK_FOO_IDS CHECK (
        (FooId1 IS NULL AND FooId2 IS NULL) 
        OR 
        (FooId1 IS NOT NULL AND FooId2 IS NOT NULL)
    );
    

内容的提问来源于stack exchange,提问作者Bill Tür stands with Ukraine

火山引擎 最新活动