累计连续总和是按指定顺序逐行累加的窗口计算,如sum() over(order by time),而普通sum()需group by且压缩行数;关键区别在于是否保留原行数及是否依赖排序逻辑。

什么是累计连续总和,和普通 SUM() 有啥区别
累计连续总和(running sum)是指按某列顺序逐行累加前面所有行的值,比如订单按时间排序后,每行显示“截至当前订单的总金额”。它不是全表求和,也不是按 GROUP BY 分组求和,而是依赖 ORDER BY 定义的逻辑顺序做逐行累积。
普通 SUM() 聚合函数必须配合 GROUP BY 使用,会压缩行数;而 SUM() OVER 是窗口函数,保留原始行数,只在指定窗口内计算——关键就在 OVER 子句怎么写。
SUM() OVER 的最小可用写法:必须带 ORDER BY
如果漏掉 ORDER BY,SUM() OVER() 默认把整张表当一个窗口,结果就是每行都显示全表总和,不是累计值。真正实现“逐行累加”,ORDER BY 不可省略,且排序字段需有业务意义(如时间、ID)。
常见错误示例:
SELECT id, amount, SUM(amount) OVER() AS wrong_total FROM orders;
正确写法(按时间递增累计):
SELECT id, order_time, amount, SUM(amount) OVER (ORDER BY order_time, id) AS running_sum FROM orders;
-
ORDER BY order_time, id确保时间相同时有稳定排序,避免窗口行为不可预测 - 不加
PARTITION BY表示全表作为一个窗口;若要按用户分组各自累计,加上PARTITION BY user_id - 某些数据库(如 MySQL 8.0+、PostgreSQL、SQL Server)支持;SQLite 和旧版 MySQL 不支持窗口函数
处理重复时间戳或需要严格递增序号的场景
当排序字段存在大量重复值(如日志按天分区、批量导入数据时间相同),仅靠 ORDER BY order_time 会导致“同时间的多行共享同一个累计值”,因为窗口默认是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,而 RANGE 会把相同排序值的行归为一组。
解决办法是显式改用 ROWS 模式,并确保排序唯一:
SELECT id, order_time, amount,
SUM(amount) OVER (
ORDER BY order_time, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_sum
FROM orders;
-
ROWS按物理行序累加,不会合并相同order_time的行 - 如果没天然唯一字段,可加
ROW_NUMBER() OVER (ORDER BY order_time, id)辅助排序,但通常直接用主键id更轻量 - Oracle 和 PostgreSQL 默认用
RANGE;SQL Server 和 MySQL 8.0 默认用ROWS,但显式写出更安全
性能与 NULL 值的隐性影响
累计求和本身不慢,但窗口函数的执行计划依赖排序字段是否有索引。如果 ORDER BY 字段无索引,数据库可能触发临时排序,大表时明显变慢。
- 确保
ORDER BY列(如order_time或(user_id, order_time))建了索引 -
NULL值在排序中默认排最前(SQL 标准),若业务中order_time IS NULL很多,它们会先被累计,可能扭曲业务含义;建议提前过滤或用COALESCE(order_time, '1970-01-01')统一处理 - 如果某行
amount是NULL,SUM()会跳过它(符合 SQL 聚合规则),但累计值会出现“断层”——看起来像少加了一笔,实际是NULL不参与运算
真正容易被忽略的是:窗口函数的 ORDER BY 不仅决定计算逻辑,还决定了最终结果集的呈现顺序——除非外层再套 ORDER BY,否则输出顺序不保证和窗口排序一致。










