在SQL的实际业务处理中,我们经常需要对数据先按指定维度分组,再在分组内部完成累加求和的计算,比如统计每个部门每月的累计销售额、每个用户每次消费的累计金额等,这类需求可以通过窗口函数SUM OVER高效实现。

SUM OVER窗口函数的基本语法
SUM OVER是SQL中用于计算累加求和的窗口函数,基础语法结构如下:
SUM(求和字段) OVER (
[PARTITION BY 分组字段]
[ORDER BY 排序字段 [ASC|DESC]]
[ROWS BETWEEN 窗口范围起点 AND 窗口范围终点]
) AS 累加结果字段
其中各个子句的作用分别是:
- PARTITION BY:指定分组的字段,数据会先按这个字段拆分成分组,累加操作在每个分组内独立进行
- ORDER BY:指定分组内数据的排序规则,累加会按照这个顺序依次计算
- ROWS BETWEEN:指定累加的窗口范围,不写的话默认是从分组内第一行到当前行
基础分组累加求和示例
假设我们有一张销售记录表sale_record,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| dept_id | INT | 部门ID |
| sale_month | VARCHAR | 销售月份 |
| sale_amount | DECIMAL | 当月销售额 |
现在需要统计每个部门每个月的累计销售额,也就是按部门分组,按月份顺序累加销售额,实现SQL如下:
SELECT
dept_id,
sale_month,
sale_amount,
-- 按部门分组,按月份升序累加销售额
SUM(sale_amount) OVER (
PARTITION BY dept_id
ORDER BY sale_month ASC
) AS cumulative_sale_amount
FROM sale_record
ORDER BY dept_id, sale_month;
上面的查询中,PARTITION BY dept_id会让数据先按部门拆分,每个部门的数据独立计算累加;ORDER BY sale_month ASC指定了分组内按月份从小到大排序,累加会从第一个月的数据开始,依次加到当前月,最终得到每个部门每个月的累计销售额。
指定累加窗口范围
默认的SUM OVER累加范围是分组内从第一行到当前行,如果需要调整累加的范围,可以使用ROWS BETWEEN子句。比如我们需要统计每个部门当前月以及前两个月的累计销售额,实现如下:
SELECT
dept_id,
sale_month,
sale_amount,
-- 累加当前行以及前两行的数据
SUM(sale_amount) OVER (
PARTITION BY dept_id
ORDER BY sale_month ASC
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS three_month_cumulative_amount
FROM sale_record
ORDER BY dept_id, sale_month;
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW的含义是窗口范围从当前行的前两行开始,到当前行结束,这样就能得到最近三个月的累计销售额。
带条件的分组累加求和
如果需要先过滤部分数据再计算累加,可以在OVER子句外面套一层子查询,或者在FROM后面先过滤数据。比如我们只需要统计销售额大于1000的记录的累计金额,实现如下:
SELECT
dept_id,
sale_month,
sale_amount,
SUM(sale_amount) OVER (
PARTITION BY dept_id
ORDER BY sale_month ASC
) AS cumulative_amount
FROM (
-- 先过滤销售额大于1000的记录
SELECT * FROM sale_record WHERE sale_amount > 1000
) AS filtered_record
ORDER BY dept_id, sale_month;
使用注意事项
- 如果分组内没有指定ORDER BY子句,那么PARTITION BY划分的分组内所有行会被视为同一个顺序,SUM会计算整个分组的总和,而不是累加效果
- 排序字段如果有重复值,默认情况下相同排序值的行的累加结果会相同,都是这些行的总和,如果需要区分相同排序值的顺序,可以在ORDER BY后面增加其他唯一字段
- 不同数据库对窗口函数的支持略有差异,比如MySQL从8.0版本开始支持窗口函数,低版本无法使用,使用前需要确认数据库版本
窗口函数SUM OVER不会改变原表的行数,只是在每一行后面新增累加结果的列,适合需要保留原始明细同时查看累加值的场景。