left join中where过滤右表字段会剔除null行,导致等效inner join;应将条件移至on子句,同时注意字段类型、空格、大小写及聚合函数count的语义差异。

WHERE里对右表字段加条件,直接过滤掉NULL行
LEFT JOIN本意是保留左表全部记录,右表没匹配上的字段填NULL。但一旦在WHERE子句里写t2.status = 'active',数据库会把所有t2.status为NULL的行剔除——结果等价于INNER JOIN,左表“丢失”了。
实操建议:
- 想保留左表所有行,同时只关联右表中
status = 'active'的数据,把条件挪到ON子句:ON t1.id = t2.t1_id AND t2.status = 'active' - 用
SELECT *先看一眼结果:如果右表字段批量为NULL,说明连接本身失败;如果只有部分为NULL但又被WHERE干掉了,大概率是这个坑 - 检查
EXPLAIN输出里的Extra列,若出现Using where; Using join buffer,再结合右表字段全NULL,基本可锁定是WHERE误伤
连接字段类型或值不一致导致匹配失败
LEFT JOIN不是“尽力而为”,而是严格按=判断。哪怕左表user_id是INT、右表user_id是VARCHAR('123 ')(带空格),也会因隐式转换或比较规则不匹配而无法关联。
实操建议:
- 用
SELECT LENGTH(t2.user_id), t2.user_id REGEXP '^[0-9]+$'查右表字段是否有空格、前导零、非数字字符 - 显式转换再比:
ON t1.user_id = CAST(t2.user_id AS UNSIGNED)(仅限确认安全时) - 大小写敏感场景(如MySQL用
utf8mb4_0900_as_cs排序规则),用LOWER()统一再连:ON LOWER(t1.email) = LOWER(t2.email)
右表字段参与GROUP BY或聚合后,COUNT行为让人误以为“丢数据”
COUNT(*)和COUNT(t2.id)在LEFT JOIN下结果可能差很多——这不是丢数据,是语义差异。COUNT(*)数的是左表行数(含右表NULL行),COUNT(t2.id)只数右表非NULL的匹配行。
实操建议:
- 要统计“每个用户有多少订单”,用
COUNT(o.order_id)(order_id为主键,天然非NULL) - 要统计“用户总数”,必须用
COUNT(*)或COUNT(u.id),不能依赖右表字段 - 如果右表某字段允许
NULL(比如o.notes),别用COUNT(o.notes)判断是否存在关联,改用SUM(CASE WHEN o.order_id IS NOT NULL THEN 1 ELSE 0 END)
子查询作右表时,ON条件写错位置
当右表是子查询(如(SELECT ... FROM orders WHERE state = 'paid') AS o),很多人习惯把筛选条件放在WHERE,却忘了子查询已提前过滤——这时ON里再写o.currency_code = a.currency_code,若子查询没包含该字段或值为空,就彻底断连。
实操建议:
- 子查询尽量只选必要字段,且确保
ON涉及的字段都在子查询SELECT中 - 若需多条件关联,优先在子查询内完成过滤,而不是靠
ON补救 - 实在要跨字段限制,确认子查询结果里该字段不为
NULL:SELECT ..., currency_code FROM orders WHERE state = 'paid' AND currency_code IS NOT NULL
ON、再WHERE,中间还夹着子查询物化。任何一步的字段缺失、类型漂移或NULL逻辑误用,都会让“左表还在,但看起来像丢了”。











