LEFT JOIN 在 WHERE 中引用右表字段会退化为 INNER JOIN;因 JOIN 先执行生成含 NULL 的中间结果,WHERE 再过滤掉右表字段为 NULL 的行,导致左表未匹配行丢失。

WHERE 里用了右表字段,LEFT JOIN 就失效了
不是数据库“故意改写”,而是 SQL 执行顺序决定的:先完成 LEFT JOIN(生成含 NULL 的中间结果),再执行 WHERE 过滤。一旦 WHERE 条件涉及右表字段(比如 d.dim_month > '2025-07-18'),所有该字段为 NULL 的行就会被筛掉——左表没匹配到右表的记录全丢了,语义上等同于 INNER JOIN。
ON 和 WHERE 的过滤时机完全不同
ON 是连接时用的,决定“哪些右表行能连上来”;WHERE 是连接后用的,决定“最终留哪些行”。关键区别在于:
-
ON t.trade_dt = d.cal_dt AND d.dim_month > '2025-07-18'→ 只让满足日期+月份条件的右表行参与连接,不满足的连不上,但左表行仍保留,d.dim_month为NULL -
WHERE d.dim_month > '2025-07-18'→ 先连完(包括NULL行),再把NULL行干掉 - 对左表字段的
WHERE(如t.trade_dt IS NOT NULL)不影响 LEFT 语义,因为它不筛右表缺失行
怎么一眼判断 LEFT 是否已退化
检查 WHERE 子句里有没有出现右表字段,且不是 IS NULL 或 IS NOT NULL 判断:
- 有
WHERE d.dim_month = '2025-07'→ 退化 - 有
WHERE d.dim_month IS NULL→ 没退化,反而专挑没匹配上的行 - 有
WHERE d.dim_month > '2025-07' OR d.dim_month IS NULL→ 没退化,但逻辑变复杂,慎用 - 只过滤左表(
WHERE t.status = 'done')→ 安全
真实场景中容易忽略的坑
动态 SQL(比如 MyBatis 的 )最容易踩这个雷:前端传了 dept_name,后端拼进 WHERE,一加就变 INNER JOIN,但开发可能根本没意识到关联语义已变。更隐蔽的是复合索引失效问题——把条件从 WHERE 挪到 ON 后,如果右表没建 (cal_dt, dim_month) 这类覆盖索引,性能反而下降。所以改写前得看执行计划,不能只盯语义。










