SUM(COUNT)算错和DISTINCT无效,是因为JOIN导致中间结果集膨胀;正确做法是先聚合再JOIN,或用窗口函数、EXISTS等避免膨胀,并注意ON与WHERE的区别及数据一致性根源。

为什么SUM(COUNT)会算错,而DISTINCT又没用
一对多JOIN后直接套SUM()或COUNT(),结果总是偏大——这不是函数错了,是中间结果集已经膨胀。比如用户表1行 + 订单表3行JOIN后变成3行,SUM(order.amount)就加了3次同一用户的订单金额。这时候加DISTINCT也没用:只要SELECT里带了子表字段(如order.created_at),每行都是“不同”的,DISTINCT根本去不掉重复的用户信息。
先聚合再JOIN,比在结果上擦屁股更可靠
真正该做的,是在JOIN之前就把右表压成一行。不是让数据库拼出3行再删2行,而是让它只吐1行你需要的值。
- 要总数?用子查询
GROUP BY user_id提前算好COUNT(*)、SUM(amount) - 要最新一条?用窗口函数
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)标序号,外层AND rn = 1过滤 - 只判断是否存在?直接换
EXISTS,不JOIN、不传字段、不膨胀
注意:ROW_NUMBER()的ORDER BY字段必须有索引,否则大表会慢;rn = 1必须写在ON条件里,写进WHERE会让LEFT JOIN退化成INNER JOIN。
LEFT JOIN里WHERE和ON写错位置,等于自废武功
想查“用户+他最近30天的订单”,却把时间条件写在WHERE里:
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.created_at >= '2026-09-01'
这实际筛掉了所有没订单的用户——因为o.created_at为NULL时,WHERE直接判假。正确做法是挪到ON:
LEFT JOIN orders o ON u.id = o.user_id AND o.created_at >= '2026-09-01'
这样没订单的用户仍保留,o.amount为NULL,语义才对。
别迷信DISTINCT,它掩盖问题比解决问题多
DISTINCT只对整行生效,且强制触发临时表排序,大数据量下性能陡降。更危险的是:如果业务本该是一对一,但因数据脏(比如外键没约束、历史遗留重复插入)导致一对多,DISTINCT会把这个问题完全藏起来,等哪天要导出明细或加新字段时才爆雷。
真正该检查的,是右表连接键是否有唯一约束、是否漏建索引、是否在应用层重复提交——这些才是重复的根因。JOIN只是照镜子,别怪镜子照得太多。

















