sum() over (order by ... rows between unbounded preceding and current row)是跨数据库最稳妥的累计求和写法:必须显式指定order by和rows帧,否则语义不明确或报错;partition by须置于order by前;重复排序值需加唯一列保障稳定性。

什么是 SUM() OVER (ORDER BY ... ROWS UNBOUNDED PRECEDING)
这不是一个“功能开关”,而是一条必须写全的窗口函数语法。漏掉 ORDER BY 会报错或结果不可控,ROWS 关键字也不能省——有些数据库(如 PostgreSQL)允许省略,但 MySQL 8.0+ 和 SQL Server 强制要求显式声明行模式,否则默认行为可能不是你想要的累加。
核心逻辑是:按指定列排序后,对当前行及之前所有行的某列求和。它不依赖自连接或变量,天然支持并发、可重复读。
-
ORDER BY必须存在,且列要有确定性(避免相同值导致累加顺序漂移) - 如果业务上需要“按时间累加但同秒有多条”,建议补上
ORDER BY created_at, id防止非预期排序 -
UNBOUNDED PRECEDING表示从分区第一行开始,到当前行为止;不能写成UNBOUNDED FOLLOWING或漏掉PRECEDING
MySQL 8.0+ 中最简可用写法
MySQL 对窗口函数支持较晚,早期版本(ERROR 1064。确认版本后,以下是最小可运行模板:
SELECT id, amount, SUM(amount) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING) AS running_total FROM orders;
注意点:
- 别用
ORDER BY created_at DESC想实现“倒序累加”——那其实是从最新一条开始往下加,不符合常规业务语义 - 如果
id不连续或有空缺,不影响累加逻辑,窗口函数只看排序位置,不看数值间隔 - 字段别名(如
running_total)不能在WHERE或HAVING中引用,得用子查询或 CTE 包一层
常见错误:为什么累加结果全是 NULL 或单值?
典型现象是整列 running_total 都是 NULL,或所有行值都一样。大概率是下面几个原因:
- 源数据中
amount字段本身为NULL——SUM()遇到NULL会跳过,但如果全为NULL,结果就是NULL;用COALESCE(amount, 0)预处理 - 误写了
PARTITION BY却没意识到分区重置逻辑,比如SUM(...) OVER (PARTITION BY status ORDER BY id ...)导致每个状态内重新累加 - 排序字段含大量重复值且无次级排序,数据库自由选择顺序,导致每次执行结果不一致(尤其在没有主键参与
ORDER BY时) - 用了
RANGE UNBOUNDED PRECEDING而非ROWS,在某些数据库中(如 PostgreSQL),RANGE会对相同排序值做“合并累加”,行为更难预测
和自连接/变量方案比,有什么实际差异?
老式累加常靠 @sum := @sum + amount 变量或 JOIN 自身,但它们在真实场景中容易出问题:
- 变量方式依赖执行顺序,
ORDER BY必须出现在同一语句中,且 MySQL 8.0+ 默认禁用变量赋值用于计算(需设sql_mode) - 自连接方案复杂度是 O(n²),10 万行订单表可能卡住;窗口函数是 O(n log n),由引擎优化
- 变量无法在视图中安全复用,窗口函数可以封装进视图、物化 CTE,也支持下推到联邦查询(如 TiDB、Doris)
- 如果你要加条件过滤(比如“只累加已支付订单”),变量方案很难动态控制起点,而窗口函数配合
FILTER(PostgreSQL)或子查询前置过滤更清晰
真正麻烦的不是写法,而是想清楚“累加基准是什么”——按插入顺序?业务时间?还是某个状态变更时间?这个排序依据一旦定错,后面所有结果都偏移,而且不容易被测试发现。










