JOIN后SUM翻倍是因为JOIN在聚合前执行,导致主表一行被复制多次(如1个订单匹配3条明细,则金额被累加3遍);唯一可靠解法是先对多端表按关联键预聚合再JOIN,或用SUM() OVER(PARTITION BY 主表唯一键)避免行膨胀。

为什么JOIN后SUM结果会翻倍?
根本原因是JOIN产生了笛卡尔积式重复行。比如订单表orders和订单明细表order_items一对多关系,一个订单有3条明细,JOIN后该订单的金额字段就会被复制3次——如果直接对orders.amount用SUM(),就相当于把同一笔订单加了3遍。
用子查询先聚合再JOIN
最稳妥的做法是把聚合逻辑“上提”到JOIN之前,避免在重复行上计算。核心思路:先按业务主键(如order_id)在明细表里算好汇总值,再和主表关联。
- 错误写法:
SELECT o.id, SUM(oi.price) FROM orders o JOIN order_items oi ON o.id = oi.order_id GROUP BY o.id(若o.id在oi中有多行,SUM会放大) - 正确写法:
SELECT o.id, COALESCE(i.total_price, 0) 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 -
LEFT JOIN比INNER JOIN更安全,能保留没有明细的订单 - 子查询里必须
GROUP BY明细表的外键(如order_id),否则聚合无意义
用窗口函数避免JOIN膨胀
当需要同时返回明细行和汇总值(比如每行显示“本订单总金额”),窗口函数比传统JOIN更干净,不引入重复行。
SELECT oi.order_id, oi.price, SUM(oi.price) OVER (PARTITION BY oi.order_id) AS order_total FROM order_items oi- 如果还需关联订单信息,用这个结果再LEFT JOIN
orders,而不是反过来 - 注意:
SUM() OVER不改变行数,但SUM() GROUP BY会压缩行数——选哪种取决于你要输出的维度 - 部分老版本MySQL(
用DISTINCT配合聚合要极其谨慎
SUM(DISTINCT ...)看似能去重,但只适用于数值本身重复的极少数场景(比如多个明细行价格碰巧相同),绝不能用于解决JOIN导致的逻辑重复。
- 例如:
SUM(DISTINCT oi.price)会把同价的不同商品合并计算,完全违背业务语义 -
COUNT(DISTINCT oi.id)可以统计明细条数,但SUM(DISTINCT ...)对金额类字段基本没意义 - 真正需要去重的是行结构,不是数值——所以必须从JOIN逻辑或聚合时机入手,而非在
SUM里硬加DISTINCT
最容易被忽略的是:问题根源不在聚合函数本身,而在JOIN引入的隐式重复。盯着SUM函数调参数不如先看执行计划里rows是否异常放大。

















