移动总和即逐行累加的运行总和,必须用SUM() OVER()配合ORDER BY和ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW实现,缺ORDER BY则结果为全表总和而非累计,显式指定窗口帧更安全且跨数据库兼容。

什么是移动总和,SUM OVER怎么配窗口框架
移动总和(Running Sum)不是对整列求和,而是按顺序逐行累加,每行结果包含“从起点到当前行”的和。在 SQL 中必须用 SUM() OVER() 实现,但光写 SUM(col) OVER() 不行——它默认是整个分区的总和,不是移动的。
关键在 ORDER BY 和 ROWS BETWEEN。没 ORDER BY,窗口无序,移动总和没意义;没显式定义行范围,数据库可能按实现默认用 UNBOUNDED PRECEDING TO CURRENT ROW(多数引擎如此),但别依赖这个隐式行为。
-
ORDER BY date是必须的,且字段需有确定排序(比如时间戳、自增 ID) - 显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW更安全,尤其跨数据库时 - 如果只想算最近 3 行(含当前),用
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
PostgreSQL / MySQL 8.0+ / SQL Server 的写法差异
语法主体一致,但细节有坑:
- MySQL 8.0+ 支持完整窗口函数,但
ORDER BY必须存在,否则报错Window 'w' with order by is required - PostgreSQL 允许
ORDER BY缺失(此时等价于无序聚合),但移动总和会失去意义,实际等于整列 SUM - SQL Server 对
ROWS框架支持好,但若排序字段有重复值,相同值的行会得到相同累计值(因为默认按排序键“并列处理”),需加唯一排序字段如ORDER BY date, id
通用写法示例(兼容性高):
SELECT date, sales, SUM(sales) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total FROM orders;
常见错误:NULL 值、分组、性能卡点
三个高频问题,一写就错:
-
SUM()遇到NULL自动跳过,但如果整组都是NULL,结果是NULL而非0。需要补COALESCE(SUM(...), 0) - 想按用户分组做各自移动总和?必须加
PARTITION BY user_id,否则所有用户混在一起算 - 大数据量时,
ORDER BY字段没索引,SUM OVER会触发全表排序,比普通聚合慢数倍。检查执行计划里是否有WindowAgg+Sort节点
替代方案:自连接 or 子查询真的更慢吗
有人用 (SELECT SUM(t2.sales) FROM orders t2 WHERE t2.date 模拟移动总和,逻辑对但性能差。原因很实在:
- 子查询对主表每行都重新扫描一次,复杂度 O(n²),10 万行可能秒变分钟级
-
SUM OVER是单次扫描 + 累加,O(n),且多数引擎能向量化或流水线处理 - 唯一例外:极小数据集(
真正容易被忽略的是排序稳定性——如果 ORDER BY 字段有大量重复值,不同数据库对“同序行”的累计顺序可能不一致,线上跑批时结果偶然波动,得加二级排序字段兜底。

















