SQL中的常规分组聚合通过group by子句实现,会将同一分组的所有行折叠为单行,仅能输出聚合函数计算后的结果,无法满足分组内保留原始数据同时执行自定义聚合逻辑的需求。窗口函数可以在不改变原数据行结构的前提下,在指定分组范围内执行计算,通过复合调用不同的窗口函数,能够灵活实现各类自定义分组内聚合逻辑。

窗口函数的基本使用规则
窗口函数的核心是通过over()子句定义计算的范围,基本语法结构如下:
函数名(参数) over (
partition by 分组列
order by 排序列 [asc|desc]
rows between 起始范围 and 结束范围
)
其中partition by用于指定分组的列,作用类似group by,但不会折叠数据行;order by用于指定分组内的排序规则;rows between用于指定分组内计算的数据行范围,默认是分组内的所有行。
常见分组内自定义聚合场景实现
场景1:分组内累计求和
需求:按部门分组,计算每个员工工资在部门内的累计和,按工资升序排列。
示例表结构如下:
| id | dept | name | salary |
|---|---|---|---|
| 1 | 研发部 | 张三 | 8000 |
| 2 | 研发部 | 李四 | 10000 |
| 3 | 研发部 | 王五 | 12000 |
| 4 | 测试部 | 赵六 | 7000 |
| 5 | 测试部 | 钱七 | 9000 |
实现SQL如下:
select
id,
dept,
name,
salary,
-- 按部门分组,组内按工资升序排序,计算累计工资和
sum(salary) over (
partition by dept
order by salary asc
rows between unbounded preceding and current row
) as dept_cum_salary
from employee;
上述代码中rows between unbounded preceding and current row表示计算从分组第一行到当前行的数据,sum函数结合这个范围就实现了分组内累计求和的自定义聚合逻辑。
场景2:分组内排名与差值计算
需求:按部门分组,给员工按工资降序排名,同时计算当前员工工资与部门最高工资的差值。
实现SQL如下:
select
id,
dept,
name,
salary,
-- 分组内按工资降序排名,rank()函数处理并列排名
rank() over (
partition by dept
order by salary desc
) as dept_salary_rank,
-- 计算当前工资与部门最高工资的差值,max函数取分组内最高工资
max(salary) over (partition by dept) - salary as diff_with_max
from employee;
这里复合调用了rank()和max()两个窗口函数,分别在分组内实现了排名和最高值获取的自定义逻辑,最后通过简单减法得到差值结果。
场景3:分组内移动平均值计算
需求:按部门分组,按工资升序排序,计算每个员工前一行到后一行的工资移动平均值。
实现SQL如下:
select
id,
dept,
name,
salary,
-- 移动平均范围:前1行到后1行,共3行计算平均值
avg(salary) over (
partition by dept
order by salary asc
rows between 1 preceding and 1 following
) as moving_avg_salary
from employee;
通过调整rows between的范围,可以灵活定义分组内参与计算的数据行范围,实现不同的自定义聚合需求。
注意事项
- 窗口函数不能直接用在where子句中,因为where子句执行顺序早于窗口函数,若需要过滤窗口函数的结果,需要使用子查询。
- 如果不需要分组,可以省略
partition by子句,此时计算范围是全表数据。 - 不同的数据库对窗口函数的支持略有差异,使用时要确认当前数据库版本是否支持对应的窗口函数。
窗口函数的复合调用核心是明确每个函数的计算范围,通过partition by划分分组,order by确定排序,rows between限定计算行范围,就能组合出满足业务需求的分组内自定义聚合逻辑。