LEFT JOIN后SUM翻倍不是Bug而是必然行为,因JOIN会将主表一行复制为N行(N为子表匹配行数),导致SUM重复累加;唯一稳妥解法是用子查询或CTE对多端表按关联键预聚合,再JOIN主表,确保每行主表只对应1行聚合值。

为什么LEFT JOIN后SUM结果翻倍不是Bug,而是必然行为
因为JOIN本身会把主表一行“复制”成N行(N = 关联子表匹配行数),SUM就真的在对这N行累加。比如1个订单关联3条明细,SUM(price_paid) 就会把同一笔金额加3次——数值变大但SQL不报错,日志里也看不出异常。
用子查询或CTE对多端表预聚合是唯一稳妥解法
核心思路:不让明细数据直接参与最终GROUP BY,先按关联键(如order_id、goods_name)各自压缩成一行,再JOIN主表。这样每行主表只对应1行聚合值,SUM()自然不再翻倍。
- 子查询写法更轻量,适合单次使用:
(SELECT goods_name, SUM(in_quantity) AS total_in FROM goods_in_stock GROUP BY goods_name) AS stock - CTE写法可读性更好,支持复用多个预聚合结果,执行计划通常与子查询等价
- 别在子查询里漏掉
GROUP BY——这是切断行复制的关键,漏了等于白做 - JOIN键必须和子查询的
GROUP BY字段严格一致,比如子查询按sale_id分组,外层就得用ON s.id = ls.sale_id
别用DISTINCT或COUNT(DISTINCT)硬凑SUM逻辑
DISTINCT对COUNT有效,但对SUM完全无效:SUM(DISTINCT amount) 是去重金额值,不是去重行;SUM() 本身不接受DISTINCT修饰符(MySQL 8.0+虽支持语法,但语义错误)。
-
COUNT(DISTINCT id)可救计数类需求,但掩盖不了聚合逻辑缺陷 -
DISTINCT *看似行数回落,实则可能合并不同业务含义的合法行(比如同一用户两笔同金额订单) - 大数据量下
DISTINCT触发临时表+文件排序,比预聚合慢得多,且无法走索引
检查是否真存在一对多关系,比改SQL更优先
先确认问题根源,再动手改。盲目预聚合可能掩盖真实数据异常。
- 对每个JOIN字段跑:
SELECT join_column, COUNT(*) FROM table GROUP BY join_column HAVING COUNT(*) > 1 - 查约束:
SHOW CREATE TABLE table_name,看字段是否有UNIQUE或PRIMARY KEY - 如果右表是视图或子查询,重复可能藏在中间层——把它单独拎出来执行一遍
- 执行计划里若
rows_examined远大于左表行数,基本就是JOIN膨胀了
实际写的时候最容易漏掉的是:预聚合子查询的GROUP BY字段和外层JOIN条件没对齐,或者忘了处理NULL(比如LEFT JOIN未匹配时,聚合字段为NULL,SUM(NULL)返回NULL而非0)。这些细节不显眼,但一出错就全盘失准。










