left join后where过滤右表字段会丢弃null行,因where在连接后执行,使左表数据丢失;正确做法是将右表条件移至on子句,如on t1.id = t2.t1_id and t2.status = 'active'。

LEFT JOIN 后 WHERE 过滤右表字段,直接丢掉 NULL 行
LEFT JOIN 的语义是“左表全保留”,但 WHERE 子句在连接完成之后才执行,此时右表没匹配上的行,所有字段都是 NULL。只要 WHERE 条件里出现对右表字段的判断(比如 t2.status = 'active' 或 t2.id IS NOT NULL),这些 NULL 行就会因布尔结果为 FALSE 或 UNKNOWN 被整行剔除——左表数据也就跟着没了。
常见错误现象:
- 明明写了
LEFT JOIN,结果行数却和INNER JOIN一样多 - 左表某些本该有记录的 ID,在结果里完全消失
-
EXPLAIN显示实际用了ref或range索引,但返回空
ON 和 WHERE 的执行阶段完全不同
SQL 执行顺序固定为:FROM → JOIN(含 ON)→ WHERE → GROUP BY → HAVING → SELECT → ORDER BY。这意味着:
-
ON是连接时的“准入规则”:决定哪些右表行能跟当前左表行配对,不满足的右表行直接跳过,但左表行照常保留(右侧填NULL) -
WHERE是连接后的“筛子”:作用于已生成的临时结果集,整行不满足就扔,不管左表多无辜 - 把右表过滤条件错放
WHERE,等于让数据库先拼出完整中间表,再一刀砍掉所有右表字段不符合条件的行——包括那些右表为空的左表行
怎么改?把右表条件挪进 ON 子句
想保留左表全部、又只连上符合条件的右表数据,唯一可靠方式是把过滤逻辑写进 ON,而不是 WHERE。
错误写法:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.t1_id WHERE t2.status = 'active';
正确写法:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.t1_id AND t2.status = 'active';
注意几个关键点:
- 不能写成
AND COALESCE(t2.status, 'inactive') = 'active'——ON中COALESCE对NULL无效,必须显式处理分支,如t2.status = 'active' OR t2.status IS NULL - 如果右表条件含
IS NULL(比如查“没关联记录”的左表行),这个可以放心放WHERE,例如WHERE t2.id IS NULL - 多表 JOIN 时,每个
ON只能引用它左边已声明的表和当前正在 JOIN 的右表,不能跨表引用尚未引入的别名
容易被忽略的 NULL 陷阱和兼容性细节
很多人以为 IN 能兜住 NULL,比如写 t2.status IN ('active', NULL),但这是错的:NULL IN (...) 永远返回 UNKNOWN,等价于 FALSE。真正安全的写法只能是显式拆开:
t2.status = 'active' OR t2.status IS NULL- 或者用
CASE配合ON实现更复杂的默认逻辑,比如 “status 为空时看 created_at 是否在 30 天内”
另外,WHERE 里用 COALESCE(t2.col, 'default') 不仅会丢数据,还会让优化器难以利用 t2.col 上的索引——因为表达式改变了原始列值,索引无法直接命中。











