NULL与NULL不相等,ON条件中=会丢弃含NULL的行;应使用IS NOT DISTINCT FROM或COALESCE/OR显式处理NULL匹配,且右表过滤条件须放在ON而非WHERE中。

ON条件里写 = 就会丢掉所有含NULL的行
这是最常踩的坑:你写 ON t1.code = t2.code,哪怕两边都是 NULL,这行也不会匹配上。因为 SQL 的三值逻辑里,NULL = NULL 不是 TRUE,而是 UNKNOWN;而 JOIN 只保留条件为 TRUE 的行。
后果很直接:INNER JOIN 会彻底跳过这些行;LEFT JOIN 虽然保留左表行,但右表字段全为 NULL——不是“匹配上了”,而是“没找到匹配”。
- 别指望数据库自动把两个
NULL当相等——它不会 -
WHERE阶段补救无效:JOIN 已经做完,丢掉的行找不回来了 - 哪怕业务上“都空”意味着“同一类”,SQL 也默认不认这个逻辑
用 IS NOT DISTINCT FROM 最干净(PostgreSQL / SQL Server 2022+)
这是 SQL 标准里专为 NULL 安全比较设计的操作符,语义明确:两个值“不可区分”就视为相等,NULL IS NOT DISTINCT FROM NULL 返回 TRUE。
示例:
SELECT * FROM orders o LEFT JOIN users u ON o.user_id IS NOT DISTINCT FROM u.id;
- 支持索引(PostgreSQL 中可走索引),性能不打折
- 不用猜兜底值,也不怕和真实数据冲突
- 如果数据库不支持(如 MySQL、旧版 SQL Server),这条路走不通
退而求其次:用 COALESCE 或 OR 显式覆盖
当 IS NOT DISTINCT FROM 不可用时,得手动构造 TRUE 条件。
COALESCE 方案(推荐用于数值/字符串):
ON COALESCE(t1.code, -999) = COALESCE(t2.code, -999)
- 必须选一个业务中绝对不可能出现的值(比如用
'__MISSING__'代替-999) - 注意类型一致:
COALESCE(t1.code, 'N/A')和COALESCE(t2.code, 0)会隐式转换失败
OR 方案(语义最直白,但性能略差):
ON (t1.code = t2.code) OR (t1.code IS NULL AND t2.code IS NULL)
- 逻辑清晰,兼容所有数据库
- 数据库优化器可能无法高效利用索引,大数据量时留意执行计划
- 多个 NULL 列要连写多个
AND,容易出错
LEFT JOIN 里别把右表过滤条件塞进 WHERE
很多人想查“左表有、右表无或状态不符”的数据,却写成:
LEFT JOIN config c ON o.config_id = c.id WHERE c.status != 'disabled' OR c.status IS NULL
这实际等价于 INNER JOIN —— 因为 c.status IS NULL 是右表无匹配产生的 NULL,而 c.status != 'disabled' 在 WHERE 阶段会把这部分全过滤掉。
- 正确做法:把右表业务条件全挪到
ON里 - 例如:
LEFT JOIN config c ON o.config_id = c.id AND c.status != 'disabled' - 这样即使
c.status为 NULL 或不满足条件,左表行仍保留,只是右表字段为 NULL
NULL 匹配不是“要不要做”的问题,而是“必须显式声明意图”的问题。漏掉这一层,查询结果就 quietly 错了——既不报错,也不警告,只默默丢数据。

















