NULL = NULL 返回 UNKNOWN,JOIN 仅保留 TRUE 行,故不匹配;需用 IS NOT DISTINCT FROM、COALESCE 或显式 OR 分支实现 NULL 安全比较。

不能。SQL标准下,NULL = NULL 的结果不是 TRUE,而是 UNKNOWN;而 JOIN 的 ON 条件只保留评估为 TRUE 的行,UNKNOWN 被当作不匹配处理——哪怕两边都是 NULL,也不会连上。
为什么 NULL = NULL 不成立?
因为 NULL 在 SQL 中表示“未知值”,不是空字符串、不是零、也不是一个特殊值,它本身不可比较。任何涉及 NULL 的等值判断(包括 NULL = NULL)都返回三值逻辑中的 UNKNOWN,而非 TRUE 或 FALSE。
JOIN 机制只认 TRUE:只有当 ON 表达式整体求值为 TRUE,该行才被纳入连接结果。所以:
-
INNER JOIN会直接丢弃两表连接字段均为NULL的行 -
LEFT JOIN会保留左表该行,但右表对应字段全为NULL(这是“未匹配”标记,不是“匹配成功”)
如何让 NULL 和 NULL 真正匹配?
必须显式绕过默认行为,不能依赖数据库自动识别语义上的“相等”。主流方案有三种,适用性各不相同:
-
IS NOT DISTINCT FROM:SQL:2003 标准,PostgreSQL / Oracle 23c / DuckDB 支持,语义最干净:ON a.id IS NOT DISTINCT FROM b.id—— 它把NULL视为一个可比较的“值”,NULL IS NOT DISTINCT FROM NULL返回TRUE -
COALESCE替换兜底值:适用于所有数据库,但需确保替换值在业务中绝对不出现,例如:ON COALESCE(a.id, -999999) = COALESCE(b.id, -999999) -
OR显式分支:写法冗长且难优化,易破坏索引下推:ON (a.id = b.id) OR (a.id IS NULL AND b.id IS NULL)
LEFT JOIN 中右表字段为 NULL 时,WHERE 里加条件会出什么问题?
这是最容易被忽略的陷阱:一旦你在 WHERE 子句里写了对右表字段的过滤(比如 WHERE b.status = 'active'),整条查询就退化为 INNER JOIN 效果——因为 b.status 为 NULL 的行(即没匹配上的行)会被 WHERE 直接剔除。
正确做法是把右表的业务条件移到 ON 里:
LEFT JOIN users b ON a.user_id = b.id AND b.status = 'active'
这样,即使 b.status 不满足,左表行仍保留,只是 b.* 全为 NULL。
不同数据库对 NULL 安全比较的支持差异
不是所有数据库都原生支持 IS NOT DISTINCT FROM:
- PostgreSQL:完全支持,推荐首选
- MySQL:不支持,但可用
(NULL-safe equal)操作符,如:ON a.id b.id - SQL Server:不支持
IS NOT DISTINCT FROM,也不支持,只能用COALESCE或CASE - Oracle:从 23c 开始支持
IS NOT DISTINCT FROM;此前常用DECODE(a.id, b.id, 1, 0) = 1或SYSTEm.UTL_RAW.CAST_TO_RAW等变通方式
跨库迁移或写通用 SQL 时,COALESCE 最稳妥,但务必确认兜底值不会和真实数据冲突——这点常被跳过,导致静默错误。











