LEFT JOIN 不影响分组统计本身,但会因笛卡尔积导致行数膨胀,使 COUNT(*) 等聚合失真;正确做法是先对右表子查询预聚合,再 LEFT JOIN,且 WHERE 中右表条件须移入 ON 子句。

LEFT JOIN 本身不会“影响”分组统计,真正出问题的是你没意识到它改变了行集结构——一不小心就把 1 行左表数据炸成 N 行,再 GROUP BY 就全乱了。核心对策就一条:聚合必须在 JOIN 前完成,不能靠 JOIN 后 COUNT(*) 硬扛。
为什么 LEFT JOIN 后 COUNT(*) 总是偏大
这不是函数错了,是数据被重复拉平了。比如一个订单在 order_items 表里有 3 条明细,在 order_logs 表里有 2 条操作记录,LEFT JOIN 之后这单就变成 3 × 2 = 6 行——COUNT(*) 统计的是这 6 行,不是 1 单。
-
COUNT(*)数的是连接后的总行数,和业务含义完全脱钩 -
COUNT(orders.id)在 LEFT JOIN 中也无效,因为 orders.id 不为空,照样会把空匹配行算进去 - 唯一可靠的是先对每个右表独立
GROUP BY汇总,再用主键或维度字段关联
多表 LEFT JOIN 必须用子查询预聚合
直接写 FROM a LEFT JOIN b ON ... LEFT JOIN c ON ... 是高危操作,尤其当 b 和 c 都对 a 是 1:N 关系时,笛卡尔积必然发生。
- 正确做法:把 b、c 分别写成带
GROUP BY的子查询,只返回a_key+ 聚合值(如COUNT(*)、SUM(amount)) - 子查询别名要明确,例如
(SELECT pr_code, COUNT(*) AS item_cnt FROM order_items GROUP BY pr_code) items - 主查询用
LEFT JOIN连这些子查询,ON 条件只比对键字段,不再引入新行 - 最后在主查询中
GROUP BY主表字段即可,不会再膨胀
LEFT JOIN 后 GROUP BY 字段必须来自左表且无歧义
如果你的 SELECT 里有 a.name、b.status,但 GROUP BY 只写了 a.id,MySQL 5.7+ 默认会报错;关掉 ONLY_FULL_GROUP_BY 则返回随机 b.status 值,结果不可信。
-
GROUP BY字段必须和SELECT中所有非聚合列严格一致,包括表前缀,例如GROUP BY a.id, a.name - 禁止在
GROUP BY中使用列别名(如AS dept_name),MySQL 不支持 - 如果真需要右表某个代表值(如最新日志时间),用
MAX(b.created_at)或COALESCE(MAX(b.created_at), '1970-01-01'),别直接选字段
WHERE 条件误放右表字段等于废掉 LEFT JOIN
写成 LEFT JOIN b ON a.id = b.a_id WHERE b.type = 'done',效果等同于 INNER JOIN——所有 b.type 为 NULL 的左表行都被过滤掉了。
- 右表筛选条件必须挪进
ON子句:LEFT JOIN b ON a.id = b.a_id AND b.type = 'done' - 如果条件依赖右表字段是否为空(如“只查有日志的订单”),那就别用 LEFT JOIN,改用
INNER JOIN - 调试时先
SELECT *看右表字段是否批量为NULL,这是匹配失败或条件错位的最直接信号
最易被忽略的一点:LEFT JOIN 的“左表”必须是你想保全的完整维度。数据库不会自动补月份、补用户、补产品类目——它只会把左表里已有的行拉出来配对。构造维度这件事,得你亲手做,别指望 JOIN 自己脑补。

















