UNIQUE约束和JOIN失效因SQL标准规定NULL= NULL返回UNKNOWN而非TRUE,故UNIQUE允许多NULL、JOIN跳过NULL行;需用COALESCE、CASE或显式IS NULL逻辑处理。

NULL值为什么让UNIQUE约束和JOIN都失效
SQL标准里NULL = NULL永远返回UNKNOWN,不是TRUE。所以即使两个字段都是NULL,UNIQUE约束不会报错,数据库也允许重复插入NULL;同样地,在JOIN条件里写t1.col = t2.col时,只要任一端是NULL,整行就直接被过滤掉——不是“不匹配”,而是“不参与比较”。这是语义设计,不是bug。
用COALESCE或CASE把NULL转成确定值再JOIN
最直接的办法是把NULL映射为一个业务上不可能出现的占位值(比如字符串字段用'__NULL__',数字字段用-999999),再做等值连接。但必须两端一致,且确保该占位值在真实数据中绝不会出现。
示例:
SELECT * FROM orders o JOIN customers c ON COALESCE(o.customer_id, -999999) = COALESCE(c.id, -999999);
注意:COALESCE会阻止索引使用(除非你建了函数索引);如果字段本身有NOT NULL约束,这个转换就多余了;另外,COALESCE嵌套多层时可读性下降,不如显式CASE清晰:
CASE WHEN o.customer_id IS NULL THEN -999999 ELSE o.customer_id END- 比
COALESCE更易调试,尤其涉及类型转换时(比如TEXT和INT混用)
用IS NULL + OR组合实现“NULL对NULL”的逻辑
如果不想引入幻值,就得显式写出NULL之间的相等逻辑。核心是:两个字段“都为NULL”或“都不为NULL且值相等”。
写法:
ON (o.customer_id = c.id) OR (o.customer_id IS NULL AND c.id IS NULL)
但要注意:这种写法会让优化器很难走索引,尤其是大表JOIN时性能可能骤降;某些数据库(如MySQL 5.7)甚至会拒绝在ON里用OR做JOIN条件(报错或退化为嵌套循环);PostgreSQL和SQL Server支持,但执行计划需人工检查。
替代思路(更可控):
- 先用
WHERE筛出非NULL部分做主JOIN,再用UNION ALL补上双方都为NULL的记录 - 或者用
LATERAL(PostgreSQL)或APPLY(SQL Server)做条件驱动关联
建唯一索引时如何兼容NULL——取决于数据库
想让含NULL的字段真正“唯一”,不能只靠UNIQUE声明,得看具体数据库行为:
- PostgreSQL:默认
UNIQUE索引允许多个NULL,但可以建UNIQUE INDEX ON tbl ((col)) WHERE col IS NOT NULL,把NULL排除在约束外 - SQL Server:
UNIQUE约束默认允许多NULL,但可用FILTERED INDEX实现相同效果 - MySQL(InnoDB):
UNIQUE索引把所有NULL视为相同值,只允许一个NULL——这点和其他数据库不同,容易误判
所以跨数据库迁移时,光看CREATE TABLE语句里的UNIQUE关键字,根本没法判断实际行为是否一致。

















