累计求和是什么
累计求和用 SUM(...) OVER (ORDER BY ...) 实现:在保留明细行的同时给出截至当前行的累计值,典型场景是销售额累计与账户流水余额。默认窗口帧是从分区开头到当前行,所以结果天然是累计值而非组内总量;想算组内总量就去掉 ORDER BY,只保留 PARTITION BY。要留意并列值的处理:默认 RANGE 帧会把排序键相同的行算进同一个累计结果,需要严格逐行累加时改用 ROWS 帧。
基本信息
| 语法 | SUM(值) OVER (PARTITION BY 分组 ORDER BY 排序 [ROWS 帧]) |
|---|---|
| 默认帧 | RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
| 累计与总量 | 带 ORDER BY 得累计值,只写 PARTITION BY 得组内总量 |
| 并列处理 | 默认 RANGE 帧会把排序键相同的并列行一起算入 |
| 严格逐行 | 写 ROWS UNBOUNDED PRECEDING 按物理顺序累加 |
| 适用平台 | MySQL 8.0+ 与 PostgreSQL 均支持 |
典型使用场景
- 销售额累计:按天累计出月内累计营收曲线
- 账户流水余额:每笔交易后显示当前余额
- 进度统计:累计完成量与目标对比
- 库存滚动变化:按时间累计出入库数量
- 用户成长值:累计积分随事件递增
- 分组累计:每个客户各自的累计消费金额
示例
示例
-- 全局累计
SELECT d, amount,
SUM(amount) OVER (ORDER BY d) AS running_total
FROM sales;
-- 按客户分组累计
SELECT customer_id, d, amount,
SUM(amount) OVER (PARTITION BY customer_id ORDER BY d
ROWS UNBOUNDED PRECEDING) AS running_total
FROM sales;
-- 组内总量(去掉 ORDER BY)
SELECT customer_id, d, amount,
SUM(amount) OVER (PARTITION BY customer_id) AS customer_total
FROM sales;窗口写法对照
| 写法 | 结果 | 适用 |
|---|---|---|
| OVER (ORDER BY d) | 全局累计 | 单一序列的累计值 |
| OVER (PARTITION BY k ORDER BY d) | 每组内累计 | 多客户或多设备的流水 |
| OVER (PARTITION BY k) | 组内总量 | 每行附带该组总额 |
| ROWS UNBOUNDED PRECEDING | 严格逐行累加 | 排序键可能重复时 |
| RANGE 默认帧 | 并列行合并计算 | 需要按值而非按行的累计 |
排序键重复时默认 RANGE 帧会让并列行得到相同的累计值,看起来像跳变;需要严格逐行累加就用 ROWS 帧。
累计结果依赖排序键的确定顺序,排序键不唯一时补主键做次级键,否则同一份数据两次计算的累计值可能不同。
需要「截至上一天的累计」时把条件写成严格小于当天,或在窗口前先用子查询按天聚合,避免同日多行导致重复累加。
使用要点
- 排序键要唯一或补主键,结果才可复现
- 用 ROWS 帧替代默认 RANGE 帧,得到严格逐行的累计
- 先按天聚合再开窗,避免一日多行导致累计跳变
- 分区键与排序键上建索引可减少额外排序
- 展示时要注明累计口径:含当日还是不含当日
- 大表上先过滤时间范围再开窗,减少参与排序的数据量