用SUM() OVER(),别用ROW_NUMBER();前者是标准窗口函数,语义清晰、性能可控,后者仅生成序号不解决累加,必须配合PARTITION BY和ORDER BY才能正确累计。

SQL里用ROW_NUMBER()还是SUM() OVER()做累加?
直接说结论:用SUM() OVER(),别手写自连接或子查询。前者是标准窗口函数,语义清晰、性能可控;后者在数据量稍大时就容易拖慢甚至OOM。很多人一开始想用ROW_NUMBER()来构造序号再关联求和,这属于绕路——它不解决累加,只解决排序编号。
关键区别在于:ROW_NUMBER()生成唯一序号,SUM() OVER()才真正按顺序“滚着加”。比如按订单时间逐条累计金额,必须依赖ORDER BY子句定义的逻辑顺序,而这个顺序由OVER()里的ORDER BY决定,不是靠行号硬凑。
SUM() OVER()的PARTITION BY和ORDER BY怎么配?
同组内累加,核心就是两个子句配合:PARTITION BY切分组(比如按user_id),ORDER BY定义组内顺序(比如按created_at)。漏掉任一个,结果都会错。
- 只写
PARTITION BY不写ORDER BY→ 累加变成“组内总和”,每行都显示同一值,不是累计 - 只写
ORDER BY不写PARTITION BY→ 全表按时间滚加,跨用户混在一起 -
ORDER BY字段有重复值且没加唯一键(如id)→ 同一时刻多笔订单,数据库可能任意排序,导致累计值不稳定
示例:按用户+时间累计消费
SELECT
user_id,
created_at,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY created_at, id
) AS cum_amount
FROM orders;
MySQL 5.7 或 SQL Server 2012 怎么办?它们不支持窗口函数
老版本确实没SUM() OVER(),但别急着写自连接。先确认是否真不能升级——很多线上环境其实已支持8.0+或2016+。如果确定受限,优先用变量法(MySQL)或递归CTE(SQL Server),而不是JOIN子查询。
- MySQL 5.7 变量法注意初始化和执行顺序:
@cum := @cum + amount必须和ORDER BY在同一查询中,且变量需提前SET @cum := 0 - SQL Server 2012+ 实际支持窗口函数,只是部分旧文档没写清楚;2008R2及更早才需递归CTE,但性能差、易爆栈
- 所有变量方案在含
GROUP BY或复杂WHERE时极易出错,建议只用于简单报表场景
累计聚合结果为什么和预期对不上?常见断点在哪
多数时候不是语法错,而是数据或逻辑理解偏差。最容易被忽略的是NULL和排序稳定性。
-
amount字段为NULL→SUM()会跳过它,但该行仍参与排序,导致后续累计值“少加一笔” -
ORDER BY字段含NULL → 不同数据库处理方式不同(MySQL默认NULL排最前,PostgreSQL排最后),累计起点可能偏移 - 业务上“同组”定义模糊:比如按
date(created_at)分组,但实际需要按自然日+用户,就得用DATE(created_at)而非created_at本身 - 索引缺失:当
PARTITION BY和ORDER BY字段没联合索引时,大表窗口计算可能极慢,甚至触发磁盘临时表
查问题最快方式:先把OVER()子句拆出来,单独跑SELECT *, ROW_NUMBER() OVER(...)看排序是否符合业务时序,再加SUM()。










