join后sum或count翻倍不是sql算错,而是因1行主表×n行子表生成n行中间结果导致重复累加;应先用子查询按关联键聚合再join,或用窗口函数sum() over(partition by)避免行数膨胀。

为什么JOIN后SUM或COUNT会翻倍
不是SQL算错了,是它老老实实按物理行计算:1行主表 × N行子表 = N行中间结果,SUM就把同一笔金额加了N遍。比如orders表1条记录关联order_items表3条明细,JOIN后变成3行,SUM(oi.price)就累加了3次——查出来的总金额可能比财务系统高好几倍。
用子查询先聚合再JOIN最稳
核心是把“多端表”先压缩成每关联键一行,切断笛卡尔膨胀链。别指望GROUP BY事后补救,膨胀发生在JOIN阶段,GROUP BY只是对已错乱的数据再汇总。
-
SELECT子查询必须GROUP BY关联字段(如order_id),漏掉就等于没做 - 过滤条件(如
status = 'paid')必须写在子查询里,不能挪到外层WHERE,否则无效行仍参与聚合 -
LEFT JOIN时记得用COALESCE(t.total_price, 0)处理NULL,否则前端可能报错 - JOIN条件必须和子查询
GROUP BY字段严格一致,比如子查询GROUP BY oi.order_id,外层就得写ON o.id = oi.order_id
需要保留明细行时用SUM() OVER(PARTITION BY)
当你要在每条明细行上显示“本订单总金额”,又不想让JOIN拖着主表膨胀,窗口函数比传统JOIN更干净——它不改变行数,只在原行集上附加计算。
-
PARTITION BY必须选主表唯一键(如order_id),确保每个业务单元只算一次 -
SUM(oi.price) OVER (PARTITION BY oi.order_id)返回的是每行都带本订单总额,不是压缩行数 - 如果还需关联
orders表字段,用这个窗口结果再LEFT JOIN orders,而不是反过来 - 注意老版本MySQL(
5.7及之前)不支持窗口函数,得降级用子查询
最容易被忽略的细节
问题不在SUM函数本身,而在JOIN引入的隐式重复。执行计划里rows_examined如果远大于左表行数(比如users表10万行,却扫描40万行),基本就是膨胀了。更隐蔽的是:子查询GROUP BY字段和外层ON条件没对齐、忘了处理NULL、或者右表本身就有重复user_id(没加UNIQUE约束)——这些地方一出错,整个聚合就失准。











