sum()在join后必然重复,因sql执行顺序为from→join→group by→sum(),一对多关联导致主表行被复制,使聚合值翻倍;修复须用子查询预聚合明细表(group by外键),再left join并coalesce处理null。

直接在 JOIN 后用 SUM() 累加关联表字段,结果必然重复——这不是写法错误,是 SQL 执行顺序决定的:先膨胀,再求和。修复必须切断“主表一行 → 关联表多行 → 主表字段被复制多次”这个链条。
为什么 SUM() 在 JOIN 后一定重复?
数据库执行顺序是 FROM → JOIN → GROUP BY → SUM()。只要右表对左表主键存在一对多关系,JOIN 就会生成笛卡尔展开行。比如一个订单有 3 条明细,orders 表那 1 行就会变成 3 行,SUM(order_items.price) 就把同一笔订单的 price 加了 3 遍。
- 典型现象:
SUM()结果是预期值的整数倍(2 倍、3 倍),且倍数 ≈ 右表平均匹配行数 -
SUM(DISTINCT price)完全无效:它去重的是数值本身,不是行;两笔不同订单都是 100 元,会被当做一个值累加 - 执行计划里
rows_examined显著高于左表行数,基本可确认是 JOIN 膨胀导致
用子查询预聚合是最稳的解法
核心动作:让明细表先按外键压缩成一行,再和主表关联。这样每行主表只对应 1 行聚合值,SUM() 不再有重复行可加。
- 子查询必须
GROUP BY明细表的外键(如order_id、user_id),漏掉这步等于白做 -
JOIN条件字段必须和子查询GROUP BY字段严格一致,类型也要匹配(比如都是INT,避免VARCHAR隐式转换) - 用
LEFT JOIN时,记得用COALESCE(i.total_price, 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;
多个一对多表同时关联时,必须各自预聚合
如果同时关联 order_items 和 payments,直接三表 JOIN 会产生 N × M 行爆炸(一个订单 3 条明细 + 2 笔支付 = 6 行),SUM() 会把金额各加 2 次或 3 次。
- 必须拆成两个独立子查询,分别按
order_id聚合,再通过LEFT JOIN合并到主表 - 所有过滤条件(如日期范围)必须下推到子查询内部,不能放在外层
WHERE,否则会把LEFT JOIN变成INNER JOIN - 子查询别名不能是保留字(如
order、group),MySQL 会报Error Code: 1248
用 SUM() OVER 的适用边界要清楚
当你需要“每行都显示本组汇总值”(比如每条明细行旁显示该订单总金额),SUM() OVER (PARTITION BY order_id) 是干净的选择——它不改变行数,只广播聚合值。
-
PARTITION BY字段必须是主表唯一标识(如order_id),否则仍会算错 - 不能用它聚合右表字段(如
COUNT(DISTINCT product_id) OVER),因为窗口函数无法自动去重已膨胀的行 - MySQL 5.7 及更早版本不支持
OVER,必须降级为相关子查询或预聚合 - 窗口函数不能和普通聚合函数混用在同一级
SELECT,多数数据库会直接报错
真正容易被忽略的点是:重复不是发生在 GROUP BY 或 SUM() 阶段,而是在 JOIN 阶段就已注定。看到结果翻倍,第一反应不该是调 SUM 参数,而是检查被 JOIN 的表是否和主表存在一对多关系,并立刻在关联前做收敛。











