NULL值在ON条件中永不匹配,因NULL= NULL返回UNKNOWN;需用IS NULL+OR显式处理,如(o.user_id = u.id) OR (o.user_id IS NULL AND u.id IS NULL),注意括号优先级。

NULL值在ON条件中永远不匹配
SQL标准规定,NULL = NULL 返回 UNKNOWN,而非 TRUE,因此在 JOIN ... ON 中,哪怕两列都是 NULL,也不会被关联上。这是最常被忽略的底层逻辑,直接导致关联结果缺失。
比如:SELECT * FROM a JOIN b ON a.code = b.code,当 a.code 和 b.code 同时为 NULL 时,这一行不会出现在结果里。
- 所有主流数据库(PostgreSQL、MySQL 8.0+、SQL Server、Oracle)都遵循此行为
- MySQL 5.7 及更早版本在某些模式下可能表现不同,但不可依赖
- 别试图用
COALESCE(a.code, '') = COALESCE(b.code, '')简单替代——空字符串和NULL语义不同,可能引发误匹配
用IS NULL + OR组合显式处理NULL对齐
想让两个 NULL 值“相等”,必须把 IS NULL 判断拆出来,和等值判断用 OR 组合。这是最直接、兼容性最好的写法。
示例(关联用户表与订单表,允许 user_id 同时为 NULL 的记录配对):
SELECT * FROM orders o JOIN users u ON (o.user_id = u.id) OR (o.user_id IS NULL AND u.id IS NULL);
- 注意括号:
OR优先级低于AND,不加括号会导致逻辑错误 - 如果关联字段有索引,这种写法通常无法走索引,大数据量时性能明显下降
- 若只有一侧可能为
NULL(如u.id是主键,不可能为NULL),只需写o.user_id = u.id OR o.user_id IS NULL即可
用COALESCE或CASE构造非NULL代理键(慎用)
当需要频繁按含 NULL 字段关联,且能接受语义微调时,可用 COALESCE 统一替换为某个“哨兵值”(如 -1、'__NULL__'),再做等值连接。
示例:
SELECT * FROM orders o JOIN users u ON COALESCE(o.user_id, -1) = COALESCE(u.id, -1);
- 必须确保哨兵值在原始数据中绝对不出现,否则会污染结果
-
COALESCE会隐式类型转换(如把INT和VARCHAR都转成字符串),可能触发全表扫描 - PostgreSQL 支持函数索引(
CREATE INDEX ON orders(COALESCE(user_id, -1))),但 MySQL 不支持,需权衡
LEFT JOIN + WHERE IS NULL 的场景要特别小心
当用 LEFT JOIN 查找“未匹配到”的记录时,如果关联字段含 NULL,WHERE b.id IS NULL 会同时筛出“右表无匹配”和“右表有匹配但 id 是 NULL”两种情况,极易误判。
- 正确做法是把
NULL对齐逻辑提前到ON子句中,再用WHERE过滤真正缺失的行 - 例如:想找“订单没关联到任何用户”的记录,应写
ON (o.user_id = u.id) OR (o.user_id IS NULL AND u.id IS NULL),然后WHERE u.id IS NULL AND o.user_id IS NOT NULL - 这个细节在报表统计中出错率极高,建议在测试数据里专门加入
NULL边界值验证
实际业务中,NULL关联往往暴露的是模型设计问题——比如该用外键约束却允许NULL,或本该用关联表却用了冗余字段。代码能绕过,但根源得回头理清楚。










