在数据分析与报表统计中,我们经常需要先计算分组,再在每个分组内部按时间或序号进行累计求和,这种需求被称为分组后的运行总计。使用SQL窗口函数可以用很简洁的语法完成这件事,不需要复杂的自关联。

什么是窗口函数
窗口函数是对一组行进行计算但不改变原表行数的函数。它通过OVER子句定义窗口范围。常用的聚合窗口函数包括SUM、AVG、COUNT等。当我们在SUM后面加上OVER,就可以在每一行上算出指定窗口内的合计。
分组累计求和的核心语法
要实现分组后的累计求和,关键是两个参数:
- PARTITION BY:指定分组的列,相当于GROUP BY的作用,但保留明细行。
- ORDER BY:指定组内排序,窗口函数会按这个顺序做从头到当前的累计。
基本写法如下:
SELECT
dept_id,
sale_date,
amount,
SUM(amount) OVER (
PARTITION BY dept_id
ORDER BY sale_date
) AS running_total
FROM sales;
上面代码中,PARTITION BY dept_id让数据按部门分组,ORDER BY sale_date让每个部门内按日期排序,SUM(amount) OVER会计算从组内第一行到当前行的金额合计,也就是运行总计。
示例数据与验证
假设有如下销售表sales:
| dept_id | sale_date | amount |
|---|---|---|
| A | 2023-01-01 | 100 |
| A | 2023-01-02 | 200 |
| B | 2023-01-01 | 50 |
| B | 2023-01-03 | 150 |
执行前面的SQL后,部门A在第二天running_total为300,部门B在第三天running_total为200,各自独立累计。
常见错误与注意点
忘记ORDER BY
如果只写PARTITION BY而不写ORDER BY,SUM会计算整个分组的全部和,而不是累计值。
-- 这样得到的是分组总和,不是运行总计
SELECT
dept_id,
amount,
SUM(amount) OVER (PARTITION BY dept_id) AS group_total
FROM sales;
排序字段不唯一
当ORDER BY的列有重复值时,数据库可能把相同值的所有行算成同一个累计值。若需严格逐行累计,可加上唯一主键:
SELECT
dept_id,
sale_id,
amount,
SUM(amount) OVER (
PARTITION BY dept_id
ORDER BY sale_id
) AS running_total
FROM sales;
不同数据库的兼容性
MySQL 8.0、PostgreSQL、SQL Server、Oracle都支持上述窗口函数写法。在低版本MySQL中不可用,需要改用子查询或用户变量实现。
利用窗口函数做分组累计求和,逻辑清晰且执行效率高,是日常SQL开发推荐的做法。
小结
通过SUM() OVER (PARTITION BY 列 ORDER BY 列)就可以在SQL中实现分组后的累计求和。只要记住分区控制分组、排序控制累计方向,就能应对大部分运行总计场景。