left join后左表数据“消失”是因为where中引用右表字段导致null行被过滤,正确做法是将右表筛选条件移至on子句;count(*)统计左表全部行,count(右表字段)仅统计非null匹配行。

LEFT JOIN后左表数据“消失”了,一定是WHERE里写了右表字段
LEFT JOIN本意就是保留左表全部行,但结果里左表某些行没了,90%是因为在WHERE中引用了右表字段。比如WHERE o.status = 'paid',会把右表没匹配到的行(此时o.status为NULL)整个过滤掉——这等价于把LEFT JOIN当成了INNER JOIN用。
正确做法是把对右表的筛选条件挪到ON子句里:
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid';
-
ON里的o.status = 'paid'只控制哪些右表行能连上来,不丢左表行 - 如果需要同时查“有paid订单”和“无订单”的用户,这个写法天然支持
- 若后续还要加
WHERE u.name LIKE 'A%',放心写,这是左表条件,不影响保留逻辑
COUNT(*) 和 COUNT(右表字段) 的区别必须分清
统计时最容易踩坑的是COUNT(*)和COUNT(o.id)混用。它们语义完全不同:
-
COUNT(*)数的是结果集总行数,等于左表原始行数(哪怕右表全为NULL) -
COUNT(o.order_id)只统计右表非NULL的匹配行,也就是“有多少个有效关联” - 想查每个用户有几个订单?用
COUNT(o.order_id)+GROUP BY u.id - 想筛出“至少有一个订单”的用户?不能靠
WHERE o.order_id IS NOT NULL(会丢无订单用户),而要用HAVING COUNT(o.order_id) > 0
多表LEFT JOIN顺序和索引直接影响性能
MySQL和PostgreSQL对LEFT JOIN顺序敏感:每一步都以前一步结果为基础扩展列,不是并行处理。顺序不当会导致中间结果爆炸或全表扫描。
- 优先让小表或高过滤性表做左表(比如
users比logs小得多) -
ON条件字段必须有索引,尤其是右表的外键字段(如orders.user_id) - 避免写成
LEFT JOIN A ON ... LEFT JOIN B ON ... LEFT JOIN C ON ...且C直接依赖A字段却没经过B中转——可能触发笛卡尔积 - 如果右表数据量极大,考虑先用子查询或CTE预过滤,比如
(SELECT * FROM orders WHERE status = 'paid') o
NULL值判断和COALESCE要严格区分使用场景
左表字段不会因LEFT JOIN变NULL,但右表字段一定会。这个前提决定了怎么写条件和默认值:
-
WHERE t1.name IS NULL永远不成立(除非原始数据就为NULL);真正想找“没订单的用户”,得写WHERE o.order_id IS NULL -
COALESCE(o.amount, 0)适合展示层补零,但它不能替代逻辑判断——比如WHERE COALESCE(o.amount, 0) > 0会把原本NULL但应被保留的行也过滤掉 - 聚合时若按右表字段分组(如
GROUP BY o.category),必须先WHERE o.category IS NOT NULL,否则所有NULL会被归为同一组,破坏分组语义
实际执行时最常被忽略的是:视图或子查询里嵌套了LEFT JOIN,外部再加WHERE仍可能意外丢左表行——得看执行计划里Extra是否出现Using where; Using join buffer这类提示,而不是只盯着SQL写法。











