SQL LEFT JOIN 与 WHERE 条件的隐藏坑
在日常开发中,我们经常用 LEFT JOIN
来保留左表数据,即使右表没有匹配记录也能查到结果。然而,如果不小心把右表的过滤条件写在了 WHERE
子句里,就会让 LEFT JOIN
失效,变成了“变相 INNER JOIN”。
本文通过一个真实案例,分析这个坑的成因、表现和正确写法。
1. 场景背景
我在调试一个查询时,遇到一个很奇怪的现象:
- 单独查右表子查询是有数据的
- 但是和左表
LEFT JOIN
之后,结果却是空的
SQL 如下:
SELECTb.project_code,b.account_code,b.group_code,b.group_name,p.finish_date,p.actual_finish_date,p.job_code
FROM (-- 楼栋表,按项目、账户、组团去重SELECT DISTINCTproject_code,account_code,group_code,group_nameFROM t_jsc_ysjgzj_building_account WHERE batch_number = (SELECT max(batch_number) FROM t_jsc_ysjgzj_building_account)AND delete_flag = 0AND group_code IS NOT NULL AND group_code != ''AND account_code = '0200096829000128958'
) b
LEFT JOIN t_jh_all_plan_list_detail pON b.group_code = p.gr_id
WHERE p.delete_flag = 0AND p.is_ignored = 0AND p.job_code IN ('JT00056', 'JT00019');
2. 问题原因
很多人误以为 LEFT JOIN
后写任何条件都能保留左表数据,其实不然。
当 右表无匹配行 时,右表的所有列都是 NULL
:
- 条件
p.delete_flag = 0
等价于NULL = 0
,结果是FALSE
- 条件
p.job_code IN (...)
等价于NULL IN (...)
,结果也是FALSE
于是,这些记录在 WHERE
阶段被过滤掉,相当于把 LEFT JOIN
转成了 INNER JOIN
。
3. 直观对比
写法位置 | 右表无匹配行时 | 左表数据是否保留 |
---|---|---|
条件写在 ON 中 | 条件判断发生在匹配阶段,右表为 NULL 依然保留左表 | ✅ 保留 |
条件写在 WHERE 中 | 条件判断在匹配后过滤阶段,右表 NULL 导致条件不成立 | ❌ 不保留 |
4. 正确写法
将对右表的过滤条件移动到 ON
子句中:
SELECTb.project_code,b.account_code,b.group_code,b.group_name,p.finish_date,p.actual_finish_date,p.job_code
FROM (SELECT DISTINCTproject_code,account_code,group_code,group_nameFROM t_jsc_ysjgzj_building_account WHERE batch_number = (SELECT max(batch_number) FROM t_jsc_ysjgzj_building_account)AND delete_flag = 0AND group_code IS NOT NULL AND group_code != ''AND account_code = '0200096829000128958'
) b
LEFT JOIN t_jh_all_plan_list_detail pON b.group_code = p.gr_idAND p.delete_flag = 0AND p.is_ignored = 0AND p.job_code IN ('JT00056', 'JT00019');
这样做的好处:
- 保证了
LEFT JOIN
的“左表优先保留”特性 - 避免了无匹配数据被误删
5. 总结
-
LEFT JOIN
条件要分清位置- 右表条件 → 写在 ON(保留左表数据)
- 左表条件 → 写在 WHERE(正常过滤)
-
检查 SQL 结果异常时
- 看右表字段是否在
WHERE
中被硬性约束 - 如果是,就考虑挪到
ON
中
- 看右表字段是否在
-
口诀
LEFT JOIN 不生效, 多半 WHERE 害的。 条件放在 ON, 左表才安全。 ``