SQL中NULL= NULL不成立,因三值逻辑下返回UNKNOWN而JOIN只保留TRUE行;需用OR条件、COALESCE或IS NOT DISTINCT FROM实现NULL安全关联。

因为SQL标准规定 NULL = NULL 返回 UNKNOWN,而JOIN只保留ON条件为 TRUE 的行——哪怕两边都是NULL,也不会被当作“相等”来关联。
为什么 NULL = NULL 在JOIN中永远不成立
这不是数据库实现差异,而是SQL三值逻辑(TRUE/FALSE/UNKNOWN)的底层规则。NULL代表“未知值”,两个未知值之间无法确定是否相同。所以 ON a.id = b.id 遇到任一端为NULL时,整个表达式结果就是 UNKNOWN,该行直接被JOIN过滤掉。
常见错误现象包括:
- INNER JOIN后,本该有数据的NULL行全部消失
- LEFT JOIN中,左表字段为NULL时,右表对应行完全不出现(不是右表字段为NULL,而是整行没匹配)
- FULL JOIN结果里,两边都为NULL的记录各自独立存在,形成“伪双边缺失”假象
怎么让NULL和NULL也能JOIN上
必须显式绕过三值逻辑,把NULL之间的比较转成布尔判断。最通用、兼容性最好的写法是:
ON (a.id = b.id) OR (a.id IS NULL AND b.id IS NULL)
注意:不能只写 a.id IS NULL AND b.id IS NULL,否则会漏掉非NULL的正常匹配。
其他方式需谨慎使用:
-
COALESCE(a.id, -1) = COALESCE(b.id, -1):要确保替换值(如-1)在业务中绝不会真实出现 -
a.id IS NOT DISTINCT FROM b.id:PostgreSQL原生支持;MySQL 8.0.16+ 支持但JOIN中部分版本有优化限制;SQL Server、Oracle不支持,必须手写等价逻辑
JOIN字段类型或COLLATION不一致会让问题更难排查
当字段类型不一致(如 INT vs VARCHAR)或字符集校对规则不同(如 utf8mb4_unicode_ci vs utf8mb4_0900_as_cs),MySQL会触发隐式转换,导致索引失效(EXPLAIN 中 key 为 NULL,type 为 ALL)。此时即使你写了正确的NULL处理逻辑,性能也会断崖式下降。
验证方法:
- 用
SHOW FULL COLUMNS FROM table_name LIKE 'id'查Collation和Type是否完全一致 - 用
EXPLAIN看驱动表和被驱动表的key和type字段 - 别在
ON里加COLLATE或CAST临时补救——这无法恢复索引使用
LEFT JOIN中把NULL逻辑写进WHERE是典型误操作
比如想查所有用户及其订单,同时保留用户ID为NULL的记录,却写了:
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id IS NULL
这会把所有 u.id 非NULL的用户全过滤掉,只剩NULL用户(且无订单),完全违背LEFT JOIN本意。
正确做法只有两种:
- 把NULL逻辑放进
ON条件,例如ON (u.id = o.user_id) OR (u.id IS NULL AND o.user_id IS NULL) - 或在
WHERE中只筛真正需要的业务条件,不碰JOIN字段的NULL判断
真正难处理的从来不是语法,而是当NULL既代表“缺失”,又代表“未归类”,还可能代表“系统未同步”时,你没法靠一个 OR 就区分出它的业务来源。这时候提前在ETL层填充占位符(如 'UNK' 或 -999),比在查询层硬补更可控。

















