LEFT JOIN退化为INNER JOIN是因为WHERE中对右表字段的非空条件过滤了NULL行;SQL执行顺序是先JOIN生成含NULL的中间结果,再WHERE过滤,导致左表未匹配行被整行丢弃。

LEFT JOIN 在 SQL Server 中变成 INNER JOIN 效果,不是优化器“偷偷改写”,而是 WHERE 子句里写了右表字段的非空条件,把本该为 NULL 的行筛掉了。
WHERE 里写了右表字段,就退化
SQL 执行顺序是:先完成 LEFT JOIN(生成含 NULL 的中间结果),再执行 WHERE 过滤。一旦 WHERE 中出现右表字段的非 IS NULL 条件,所有该字段为 NULL 的行——也就是左表没匹配到右表的那些行——全被丢弃。
-
WHERE o.status = 'paid'→ 退化:o.status为NULL的用户行被过滤掉 -
WHERE o.status IS NULL→ 没退化:反而只保留没订单的用户 -
WHERE o.status = 'paid' OR o.status IS NULL→ 没退化,但逻辑变复杂,容易漏数据或误判
正确做法:右表筛选条件必须进 ON
要把业务过滤逻辑绑定到连接行为本身,而不是事后过滤结果。这样既保留左表完整性,又只让符合条件的右表行参与连接。
- 错误写法:
LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' - 正确写法:
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' - 多层 LEFT JOIN 时,每层右表的筛选都要收进各自的
ON,比如:LEFT JOIN order_items oi ON o.id = oi.order_id AND oi.category = 'electronics'
动态 SQL 场景下特别危险
MyBatis、JDBC 拼接条件时,前端传了 dept_name,后端直接塞进 WHERE,LEFT JOIN 就悄悄变成 INNER JOIN,但开发可能完全没意识到关联语义已变。
- 更隐蔽的是性能问题:条件从
WHERE挪到ON后,如果右表没建(cal_dt, dim_month)这类复合索引,谓词下推可能失效,查询反而变慢 - 改写前必须看
EXPLAIN或 SQL Server 的执行计划,不能只盯语法
最容易被忽略的点是:退化不依赖数据库版本或配置,它是 SQL 标准定义的执行顺序决定的;哪怕你确认写了 LEFT JOIN,只要 WHERE 动了右表字段,语义就变了。

















