JOIN后SUM或COUNT翻倍的根本原因是JOIN在聚合前执行,导致主表一行被复制为多行;应先对多端表预分组再JOIN,或用窗口函数避免行数膨胀。

为什么JOIN后SUM或COUNT会翻倍
根本原因不是聚合函数写错了,而是JOIN在聚合之前执行,把主表一行“复制”成多行。比如一个订单关联3条明细,orders表的amount字段就会在中间结果里出现3次——SUM()真就加了3遍。查执行计划时如果看到rows列远超主表行数,基本就是这个病。
先聚合再JOIN是最稳解法
把聚合逻辑提到JOIN之前,切断行复制链条。关键动作是:对多端表(如order_items)按外键预分组,生成一行一汇总的结果,再和主表关联。
-
SELECT里所有聚合必须在子查询里完成,不能留到外层 - 子查询
GROUP BY字段必须和JOIN条件字段完全一致(类型也要匹配,比如别让INT去连VARCHAR) - 用
LEFT JOIN而非INNER JOIN,否则没明细的订单直接丢掉 - 记得用
COALESCE()把NULL转成0,否则SUM()结果可能为NULL
示例: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
需要明细行+汇总值时用窗口函数
如果既要每条明细记录,又要显示“本订单总金额”,SUM() OVER比先GROUP BY再JOIN更干净——它不改变行数,也不引入重复。
-
SUM(price) OVER (PARTITION BY order_id)算的是当前行所在订单的总价,每行都带这个值 - 若还需关联订单主表信息,用这个结果集再去
LEFT JOIN orders,而不是反过来 - 老版本MySQL(5.7及更早)不支持窗口函数,得换思路,比如用相关子查询(但性能差)或升级
示例:SELECT oi.order_id, oi.price, SUM(oi.price) OVER (PARTITION BY oi.order_id) AS order_total FROM order_items oi
DISTINCT在SUM里基本没用
SUM(DISTINCT price)是去重数值,不是去重行。两笔不同商品但价格碰巧都是100元,会被当成一个100加一遍——业务上完全错误。真正要解决的是“同一笔订单被算多次”,这只能从JOIN时机或聚合位置入手,不是在SUM参数里加个DISTINCT就能糊弄过去。
最容易被忽略的点:问题不在SUM函数本身,而在你没意识到JOIN已经把数据撑开了。盯着函数调参不如先看执行计划里rows是否异常放大。

















