sum() over (order by ts rows between unbounded preceding and current row) 是最直接、稳妥的累计求和写法,必须显式指定 rows 帧以确保按物理行序累加,避免因默认 range 行为导致重复排序值下的跳变;ts 应配合唯一列(如 id)排序,或用 row_number() 生成确定性序号,严禁用 lag() 或递归 cte 替代。

用 SUM() 配合 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 最直接
窗口函数里统计“当前行及之前所有行”的累计和,SUM() 是最常用也最稳妥的选择。关键不是写 SUM(col) 就完事,而是必须显式定义窗口帧(frame),否则在某些数据库(如 PostgreSQL、SQL Server)里默认行为可能不是你想要的累计效果。
常见错误是只写 OVER (ORDER BY ts),没加 ROWS 子句——这时 MySQL 8.0+ 会默认用 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,但 PostgreSQL 和 SQL Server 默认用的是 GROUPS 或隐式 RANGE,遇到相同排序值时会把它们全算进当前行,导致跳变式累加。
- 务必写全:
SUM(value) OVER (ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) -
ts必须是能唯一或稳定排序的字段;如果存在重复时间戳,建议追加唯一列(如id)避免歧义:ORDER BY ts, id - 别用
RANGE帧来算累计和——它按值范围聚合,不是按行位置,容易漏行或多算
遇到重复排序键时,ROW_NUMBER() 辅助排序更可靠
当业务上允许时间戳重复(比如批量插入、日志打点精度不足),仅靠 ORDER BY ts 无法保证窗口帧严格按物理顺序展开,ROWS 帧可能把同时间戳的多行同时纳入或排除。
这时候不能靠猜,得人为制造确定性顺序:
- 先用
ROW_NUMBER() OVER (ORDER BY ts, id)生成唯一序号rn - 再用
SUM(value) OVER (ORDER BY rn ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 注意:不要用
RANK()或DENSE_RANK()替代ROW_NUMBER(),它们会产生相同序号,破坏ROWS帧的逐行语义
LAG() 和递归 CTE 不适合做累计和
有人想用 LAG(sum_so_far, 1) 手动叠加,或者写递归 CTE 一行行算——这两种方式要么逻辑错(LAG() 拿不到前一行的累计结果,只能拿原始值),要么性能崩(递归 CTE 在大数据量下是 O(n²) 复杂度,10 万行就明显卡顿)。
-
LAG()返回的是上一行的 *原始值*,不是上一行的累计值,没法直接加 - 递归 CTE 虽语法上可行,但执行计划无法并行,且多数引擎对递归深度有限制(如 PostgreSQL 默认 100 层)
- 窗口函数的
SUM() OVER是单趟扫描、并行友好,是唯一推荐路径
MySQL 8.0+ 和 SQLite 3.25+ 支持标准语法,但旧版 SQLite 不行
如果你在用老版本 SQLite(no such function: SUM 或 near "OVER": syntax error。别怀疑 SQL 写法,是引擎不认。
- 确认版本:
SELECT sqlite_version();—— 小于 3.25 就得换方案(比如自连接或应用层累加) - MySQL 8.0+ 和 PostgreSQL 8.4+、SQL Server 2012+ 都支持完整窗口语法,但 SQL Server 对
ROWS帧的支持从 2012 就有,不必升级 - 别信网上“SQLite 3.20 支持窗口函数”的说法——实际是 3.25 才正式引入,3.20 只有实验性 patch










