INNER JOIN的NULL匹配恒为UNKNOWN,因其遵循SQL三值逻辑,仅保留ON条件为TRUE的行;等价于隐式添加IS NOT NULL检查与等值判断,故NULL参与的比较均被丢弃。

INNER JOIN 的 NULL 匹配永远返回 UNKNOWN
因为 SQL 使用三值逻辑(TRUE/FALSE/UNKNOWN),而 NULL = anything(包括另一个 NULL)的结果恒为 UNKNOWN。INNER JOIN 只保留 ON 条件判定为 TRUE 的行,UNKNOWN 直接被丢弃——这不是 bug,是标准行为。
常见错误现象:
- 左表某行
id为NULL,右表对应ref_id也是NULL,但 JOIN 结果里完全没这条记录 - 查右表确认该
ref_id值存在,却在 INNER JOIN 后“消失”,实际是因为该行ref_id是NULL
等价于你手动写了:WHERE t1.id IS NOT NULL AND t2.ref_id IS NOT NULL AND t1.id = t2.ref_id。数据库隐式加了这两项非空检查。
ON 条件中字段类型或拼写错误也会导致“静默丢行”
INNER JOIN 不报错,但匹配失败时直接跳过整行。这和 NULL 导致的丢失表现一致,但原因不同。
容易踩的坑:
-
ON users.id = orders.user_id写成ON users.id = orders.id→ 结果为空,无提示 - 字段类型不一致:比如
INT和VARCHAR比较,MySQL 可能隐式转换失败,PostgreSQL 直接报错 - 表别名漏写或混淆:
FROM users u JOIN orders o,但 ON 里写成users.id = orders.user_id→ 字段未识别,语法错误或取错表
验证方法:单独查左表带 IS NULL 的行数,再查右表对应字段的非空值集合,看是否真有交集。
LEFT JOIN 后 WHERE 过滤右表字段,效果等同于 INNER JOIN
这是最隐蔽的数据丢失场景。表面用了 LEFT JOIN,但一行代码就让左表“漏行”。
错误写法:LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid'
问题在哪:
- 没订单的用户,
o.status是NULL,NULL = 'paid'→UNKNOWN→ 被 WHERE 过滤掉 - 结果只剩有订单且已支付的用户,和
INNER JOIN ... AND o.status = 'paid'完全一致
正确做法:把右表业务条件放进 ON:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'。这样没订单的用户仍保留,只是 o.* 全为 NULL。
真正难的不是处理 NULL,而是选对 JOIN 类型
很多“查不到数据”的问题,根源不在字段值为空,而在本该用 LEFT JOIN 却写了 INNER JOIN。比如要查“所有用户及其最新订单”,用 INNER JOIN 就会直接剔除没下单的用户——这不是 NULL 的锅,是语义错了。
更麻烦的是混合场景:当右表关联字段本身允许为 NULL,又需要保留左表,还希望过滤右表有效数据时,必须同时处理两层逻辑——JOIN 类型决定是否保左,ON 子句决定如何连右。这时候光靠 COALESCE 或 IS NOT NULL 不够,得先厘清业务到底是“找匹配”,还是“补信息”。










