left join后sum翻倍是因为主表一行关联子表n行时会复制出n行参与聚合,导致sum、count等按物理行重复累加;正确做法是用子查询对子表按关联键预聚合(如group by user_id),再left join主表,并用coalesce处理null。

为什么LEFT JOIN后SUM会翻倍
因为主表一行关联到子表N行,JOIN就复制出N行参与后续聚合——SUM、COUNT自然放大N倍。这不是数据库bug,是关系代数的必然结果。比如查“每个用户的订单总金额”,users LEFT JOIN orders后直接SUM(orders.amount),数值比实际高几倍。
预汇总能避开中间结果集爆炸
三张百万级表JOIN可能生成上千万行临时数据,而聚合本身单次扫描即可完成。把SUM塞在JOIN之后,等于让数据库先拼出全部组合再统计;预汇总则是先对子表按user_id或order_id压缩成每逻辑单位一行,再JOIN,中间数据量从千万级压到百万级甚至更少。
- 聚合子查询必须只返回分组键 + 聚合值,例如:
SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id - 外层JOIN键必须严格匹配该分组键,且子查询需带别名(MySQL报
Error Code: 1248常因漏AS t) - 子查询里的过滤条件(如
WHERE status = 'paid')必须写在内部,挪到外层WHERE会把LEFT JOIN变成INNER JOIN
不预汇总时DISTINCT救不了SUM和AVG
COUNT(DISTINCT id)能绕过重复计数,但它只解决“个数”问题;SUM(DISTINCT amount)会去重金额值,逻辑完全错误。大数据量下DISTINCT还需哈希去重,比预汇总更慢,且无法利用索引优化。
- 适用:
COUNT(DISTINCT orders.id)统计每个客户下了几个订单 - 不适用:
SUM(DISTINCT orders.amount)算总销售额——它会把相同金额只加一次 - 真正要的是:先在子查询里
GROUP BY user_id算好每人总额,再JOIN
CTE和子查询选哪个
执行计划通常等价,但CTE更易读、可复用。比如同一张order_items表既要算总金额又要算商品数,用CTE只扫描一次;而嵌套子查询若重复写两次,可能被优化器分别执行。
- CTE适合多处引用同一聚合结果,或逻辑分层清晰的场景
- 简单单次引用,子查询更轻量,避免物化开销(尤其当明细表已有覆盖索引时)
- SQLite不支持写入式CTE,但查询类CTE可用;MySQL 8.0+、PostgreSQL 12+均推荐优先用CTE显式表达意图
SUM到底是在对多少行求和。











