累计总额是按指定列顺序逐行累加的值,必须保留原始行数;不能用group by,因其会压缩行数、丢失行序,无法生成每行对应的“截至当前”的动态和。

什么是累计总额,为什么不能用 GROUP BY
累计总额(running total)是按某列顺序逐行累加的值,比如按日期逐日累加销售额。它和分组聚合不同:GROUP BY 会压缩行数,而累计总额必须保持原始行数、每行一个结果。强行用 SUM() + GROUP BY 无法实现——它只能算每个分组的总和,不是“到当前行为止的和”。
用 SUM() OVER() 实现基础累计和
核心就是 SUM(column) OVER (ORDER BY key)。窗口函数不改变行数,OVER 子句定义“从哪开始、按什么顺序、累加到哪”。常见写法:
-
SUM(sales) OVER (ORDER BY date):按date升序,从第一行累加到当前行 -
SUM(sales) OVER (PARTITION BY region ORDER BY date):先按region分组,组内再按date累加 - 默认帧范围是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不用显式写,但要知道它隐含了“从头到当前”
示例(PostgreSQL/MySQL 8.0+/SQL Server):
SELECT date, sales,
SUM(sales) OVER (ORDER BY date) AS running_total
FROM sales_data;
ORDER BY 缺失或重复时的结果不可靠
没有 ORDER BY,SUM() OVER() 的行为是未定义的——多数引擎会按物理存储顺序累加,但这个顺序不稳定,换表、VACUUM、INSERT 顺序变都可能让结果跳变。更危险的是,当排序键有重复(如多笔同一天订单),数据库可能任意打乱这些行的相对顺序,导致累计值在重复键之间“忽高忽低”。
解决方法:
- 始终显式指定
ORDER BY,且排序字段要能唯一确定行序(如加id作为第二排序条件:ORDER BY date, id) - 避免用
ORDER BY created_at这类可能毫秒级重复的字段单独排序 - 如果业务允许,可在源头加自增
seq字段专用于排序
性能注意:大表上 ORDER BY 可能触发排序临时文件
OVER (ORDER BY ...) 本质需要对窗口数据排序。若排序字段无索引,100 万行以上就可能明显变慢,甚至因内存不足写磁盘临时文件。这不是语法问题,而是执行计划层面的开销。
优化建议:
- 确保
ORDER BY字段有索引(复合索引优先覆盖PARTITION BY + ORDER BY字段) - 避免在
WHERE条件过滤前计算全表累计和;先WHERE再OVER(子查询或 CTE) - MySQL 8.0 中,
ORDER BY后跟ROWS UNBOUNDED PRECEDING不影响性能,但加RANGE帧可能更慢(需值比较)
累计总额看着简单,但顺序依赖、排序稳定性、执行计划这三点,任何一个没踩准,查出来的数字就可能白天对、晚上错。











