join后sum虚高是执行顺序决定的必然结果:先join生成膨胀行集(如1订单→3明细→3行),再sum累加,导致金额翻倍;修复须用子查询预聚合(按order_id分组压缩明细表)或窗口函数sum() over (partition by order_id),而非修改聚合函数参数。

为什么JOIN后SUM会虚高
不是SQL写错,是执行顺序决定的必然结果:先JOIN生成膨胀行集,再SUM()累加——订单1条记录关联3条明细,JOIN后就变成3行,SUM(amount)自然加3遍。典型表现是总金额是预期值的整数倍(2倍、3倍),且倍数≈关联表中该主键的平均匹配行数。
执行计划里rows_examined远大于左表行数,基本可确认是JOIN膨胀;COUNT(*)在JOIN后也一样虚高,别以为只有SUM受影响。
用子查询预聚合切断膨胀链
核心动作是让明细表先按业务主键压缩成一行,再和主表关联。这样每行主表只对应1行聚合值,SUM()、COUNT()不再有重复行可加。
- 子查询必须
GROUP BY明细表的外键(如order_id、user_id),漏掉这步等于白做 -
JOIN条件字段必须和子查询GROUP BY字段严格一致,类型也要匹配(比如都是INT,避免VARCHAR隐式转换) - 用
LEFT JOIN时,记得用COALESCE(ord.total_amount, 0)处理NULL,否则没明细的订单会显示NULL而非0
示例:
SELECT o.id, o.order_date, COALESCE(i.total_price, 0) AS total_price FROM orders o LEFT JOIN ( SELECT order_id, SUM(price) AS total_price FROM order_items GROUP BY order_id ) i ON o.id = i.order_id;
用SUM() OVER替代JOIN获取汇总值
当你需要同时返回明细行和汇总值(比如每行显示“本订单总金额”),SUM() OVER (PARTITION BY order_id)比传统JOIN更干净——它不改变行数,只把聚合值广播到每行。
-
PARTITION BY字段必须是主表唯一标识(如order_id),否则仍会算错 - 不能用它聚合右表字段(如
COUNT(DISTINCT oi.product_id) OVER),因为窗口函数无法自动去重已膨胀的行 - MySQL 5.7 及更早版本不支持
OVER,必须降级为相关子查询或预聚合
示例:
SELECT oi.order_id, oi.price,
SUM(oi.price) OVER (PARTITION BY oi.order_id) AS order_total
FROM order_items oi;
最容易被忽略的细节
预聚合子查询的GROUP BY字段和外层JOIN条件没对齐,或者忘了处理NULL,这些都不报错,但一出错就全盘失准。还有就是中间表字段类型和主表不一致(比如一边是BIGINT一边是INT),会让索引失效,预聚合白做。多层一对多嵌套时(订单→商品→库存),行数是乘积级爆炸,SUM可能翻4倍、8倍,单靠肉眼很难排查。











