不能。SQL标准下NULL = NULL返回UNKNOWN而非TRUE,JOIN仅保留ON条件为TRUE的行,故两边均为NULL也不匹配;需用IS NOT DISTINCT FROM、COALESCE或OR显式处理。

NULL = NULL 返回 UNKNOWN,不是 TRUE
JOIN 只保留 ON 条件求值为 TRUE 的行,而 NULL = NULL 在 SQL 三值逻辑中结果是 UNKNOWN,不是 TRUE,所以不匹配。这不是数据库 bug,是所有符合 SQL 标准的引擎(PostgreSQL、MySQL、SQL Server、Oracle)的统一行为。
常见误解是“两边都是 NULL,应该连上”,但 SQL 把 NULL 视为“未知值”,而非一个确定的占位符——你无法断言两个未知是否相等,因此比较结果只能是 UNKNOWN。
-
INNER JOIN:含 NULL 的行直接被丢弃,哪怕左右字段都为 NULL -
LEFT JOIN:左表行保留,但右表对应列全为 NULL(这是“未匹配”标记,不是“匹配成功”) - 结果里出现的 NULL 有两种语义:原始数据缺失(真实 NULL),和连接失败产生的 NULL(逻辑 NULL),容易混淆
LEFT JOIN 后 WHERE 右表字段过滤 = 隐式 INNER JOIN
写 LEFT JOIN users u ON o.user_id = u.id WHERE u.status = 'active',实际效果等同于 INNER JOIN——因为没匹配上的行,u.status 是 NULL,NULL = 'active' 返回 UNKNOWN,整行被 WHERE 过滤掉。
正确做法是把右表业务条件挪进 ON:
LEFT JOIN users u ON o.user_id = u.id AND u.status = 'active'
这样即使 u.status 不满足,左表行仍保留,u.* 全为 NULL。
- 口诀:
ON控制“怎么连”,WHERE控制“连完留哪些行” - 软删字段(如
deleted_at IS NULL)、状态字段(如is_active = 1)都必须放进ON,否则失效 - 如果真要用
WHERE,得显式兼容:WHERE u.status = 'active' OR u.status IS NULL,但语义已变,慎用
让 NULL 和 NULL 匹配的三种实操方案
必须显式重写 ON 条件,不能依赖默认行为。选哪种取决于数据库版本和性能要求:
-
IS NOT DISTINCT FROM(推荐):语义最干净,a.id IS NOT DISTINCT FROM b.id表示“值相等或两者同为 NULL”。PostgreSQL、MySQL 8.0.16+、SQL Server 2022+ 支持;Oracle 和旧版 SQL Server 不支持 -
OR显式分支:兼容性最好,(a.id = b.id) OR (a.id IS NULL AND b.id IS NULL),但部分优化器无法下推索引,可能全表扫描 -
COALESCE替换:如COALESCE(a.id, -1) = COALESCE(b.id, -1),前提是-1在业务中绝对不出现;缺点是索引失效(执行计划里type=ALL或key=NULL就是信号)
别用 COALESCE(a.id, 0) = COALESCE(b.id, 0) 做连接键——它会让原本能走索引的字段变成函数表达式,JOIN 效率断崖下跌。
排查时最容易被忽略的细节
真正卡住人的往往不是语法,而是隐含约束和数据分布:
- 右表连接字段若定义为
NOT NULL,那WHERE t2.id IS NULL就永远返回空——你以为在查“没匹配”,其实是在查“不可能存在的 NULL” - 用
LENGTH()和TRIM()检查字段是否看着像 NULL,实则是空字符串或空格;'' = NULL也是UNKNOWN - 多个 JOIN 嵌套时,后一个
ON是基于前一个结果集,不是原始右表——中间某步丢了行,后面全错 - EXPLAIN 看执行计划,重点盯
type和key:如果是ALL或NULL,说明连接字段没走索引,问题大概率出在函数包装或类型不一致上

















