窗口函数
本文最后更新于:2025年3月1日 凌晨
窗口函数
窗口函数是MySQL8.0版本新增的,相对于聚合函数把所有行聚合成一行,窗口函数可以将每一行的数据进行分别处理。
一、序号函数
用来实现分组排序。
1 | |
例如:成绩表Score,序号函数皆以此表查询为例。
1 | |
1.1、ROW_NUMBER()
此函数进行排序时,相同的字段会分开排序。
查询成绩表,按分数进行排序,相同分数的不算在同一名(分开排)
如果不指定 PARTITION BY,窗口函数默认会将整个结果集视为一个分组,即不进行分组【全局排序】,计算基于所有行。
1 | |
结果:
1 | |
1.2、RANK()
此函数排序时,相同的字段会排在同一名,之后的字段排名为:当前字段排名+当前排名字段总数-1
1 | |
结果:
1 | |
1.3、DENSE_RANK()
此函数排序时,相同的字段会排在同一名,之后的字段不会受当前字段重复数影响,直接就是当前排名+1
1 | |
结果:
1 | |
放在一起对比:
1 | |
结果:
1 | |
思考:如果现在需要找出排名前3的成绩,该怎么做呢?(以dense_rank()排序)
1 | |
结果:
1 | |
二、分布函数
应用场景:查询小于等于当前薪资的比例。
2.1、PERCENT_RANK() 【不常用】
在以下场景中
PERCENT_RANK()非常有用:
- 百分位数计算:
- 当需要将数据划分为百分位数(如 90th percentile)时,
PERCENT_RANK()可以帮助确定每个值的相对位置。- 数据分布分析:
- 通过
PERCENT_RANK()可以快速了解数据在整个数据集中的分布情况。- 性能评估:
- 在评估学生成绩、员工绩效等场景中,
PERCENT_RANK()可以直观地反映某个人在整体中的表现。- 数据标准化:
- 在机器学习或统计分析中,
PERCENT_RANK()可以用于将数据标准化到[0, 1]范围内。
此函数基于排名的相对位置,公式为 (rank - 1) / (总行数 - 1)。
1 | |
结果为:
1 | |
2.2、CUME_DIST()
此函数用于计算累积分布值,表示在有序数据集中,某一项的值小于或等于当前值的比例。
-
正序(默认):统计每一项内,小于或等于此项的值与总数的比值。
公式为:
CUME_DIST(x)=小于等于 x 的行数总行数CUME_DIST(x)=总行数小于等于 x 的行数
-
倒序:统计每一项内,大于或等于此项的值与总数的比值。
公式为:
CUME_DIST(x)=大于等于 x 的行数总行数CUME_DIST(x)=总行数大于等于 x 的行数
应用场景:
1 | |
结果:
1 | |
三、前后函数
用途:返回位于当前行的前n行或后n行的expr的值
3.1、LAG() 函数
LAG() 函数用于访问当前行之前的某一行的数据。
语法:
1 | |
column_name:要访问的列。offset:偏移量,表示要访问的行数(默认为 1,即前一行)。default_value:如果偏移量超出范围(如第一行没有前一行),则返回的默认值(可选,默认为NULL)。
应用场景:
- 计算当前行与前一行的差值。
- 分析时间序列数据中的变化趋势。
示例:
假设有一组每日销售数据,需要计算每天的销售额与前一天的差值。
1 | |
结果可能如下:
| sales_date | sales_amount | previous_sales_amount | sales_difference |
|---|---|---|---|
| 2023-01-01 | 100 | 0 | 100 |
| 2023-01-02 | 150 | 100 | 50 |
| 2023-01-03 | 200 | 150 | 50 |
| 2023-01-04 | 180 | 200 | -20 |
3.2、LEAD() 函数
LEAD() 函数用于访问当前行之后的某一行的数据。
语法:
1 | |
column_name:要访问的列。offset:偏移量,表示要访问的行数(默认为 1,即后一行)。default_value:如果偏移量超出范围(如最后一行没有后一行),则返回的默认值(可选,默认为NULL)。
应用场景:
- 计算当前行与后一行的差值。
- 预测或分析未来趋势。
示例:
假设有一组每日销售数据,需要计算每天的销售额与后一天的差值。
1 | |
结果可能如下:
| sales_date | sales_amount | next_sales_amount | sales_difference |
|---|---|---|---|
| 2023-01-01 | 100 | 150 | 50 |
| 2023-01-02 | 150 | 200 | 50 |
| 2023-01-03 | 200 | 180 | -20 |
| 2023-01-04 | 180 | 0 | -180 |
3.3、LAG() 和 LEAD() 的区别
| 函数 | 方向 | 作用 |
|---|---|---|
LAG() |
向后 | 访问当前行之前的某一行的数据。 |
LEAD() |
向前 | 访问当前行之后的某一行的数据。 |
3.4、常见应用场景
- 时间序列分析:
- 计算每日、每月或每年的变化趋势。
- 例如:计算每天的销售额与前一天的差值。
- 相邻行比较:
- 比较当前行与相邻行的值,分析变化或异常。
- 例如:检查库存变化或温度波动。
- 预测或回填数据:
- 使用
LEAD()预测未来值,或使用LAG()回填历史值。
- 使用
- 数据填充:
- 对于缺失值,可以使用
LAG()或LEAD()从相邻行填充。
- 对于缺失值,可以使用
总结
LAG()用于访问当前行之前的某一行的数据,适合分析历史趋势或回填数据。LEAD()用于访问当前行之后的某一行的数据,适合预测未来趋势或分析未来变化。- 两者在时间序列分析、相邻行比较和数据填充等场景中非常有用。如果需要分析数据的变化趋势或预测未来值,
LAG()和LEAD()是强大的工具。
四、头尾函数
用于在窗口框架内访问第一行或最后一行的数据。它们通常用于计算累积值、分析趋势或提取边界值。
4.1、FIRST_VALUE() 函数
FIRST_VALUE() 函数返回窗口框架内第一行的值。
语法:
1 | |
column_name:要访问的列。PARTITION BY:可选,用于分组。ORDER BY:指定窗口内的排序。ROWS BETWEEN:可选,定义窗口框架的范围(默认为UNBOUNDED PRECEDING AND CURRENT ROW)。
应用场景:
- 计算累积值(如累积销售额)。
- 提取每组中的第一个值。
示例:
假设有一组每日销售数据,需要计算每天的销售额与第一天的销售额的差值。
1 | |
结果可能如下:
| sales_date | sales_amount | first_sales_amount | sales_difference |
|---|---|---|---|
| 2023-01-01 | 100 | 100 | 0 |
| 2023-01-02 | 150 | 100 | 50 |
| 2023-01-03 | 200 | 100 | 100 |
| 2023-01-04 | 180 | 100 | 80 |
4.2、LAST_VALUE() 函数
LAST_VALUE() 函数返回窗口框架内最后一行的值。
语法:
1 | |
column_name:要访问的列。PARTITION BY:可选,用于分组。ORDER BY:指定窗口内的排序。ROWS BETWEEN:可选,定义窗口框架的范围(默认为UNBOUNDED PRECEDING AND CURRENT ROW)。
注意:
默认情况下,LAST_VALUE() 的窗口框架是 UNBOUNDED PRECEDING AND CURRENT ROW,这意味着它只会返回当前行之前的值。为了获取整个窗口的最后一个值,需要将窗口框架设置为 UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。
应用场景:
- 计算每组中的最后一个值。
- 分析趋势或提取边界值。
示例:
假设有一组每日销售数据,需要计算每天的销售额与最后一天的销售额的差值。
1 | |
结果可能如下:
| sales_date | sales_amount | last_sales_amount | sales_difference |
|---|---|---|---|
| 2023-01-01 | 100 | 180 | 80 |
| 2023-01-02 | 150 | 180 | 30 |
| 2023-01-03 | 200 | 180 | -20 |
| 2023-01-04 | 180 | 180 | 0 |
4.3、FIRST_VALUE() 和 LAST_VALUE() 的区别
| 函数 | 作用 | 默认窗口框架 |
|---|---|---|
FIRST_VALUE() |
返回窗口框架内第一行的值。 | UNBOUNDED PRECEDING AND CURRENT ROW |
LAST_VALUE() |
返回窗口框架内最后一行的值。 | UNBOUNDED PRECEDING AND CURRENT ROW |
4.4、常见应用场景
- 累积值计算:
- 使用
FIRST_VALUE()计算从开始到当前行的累积值。 - 使用
LAST_VALUE()计算从当前行到结束的累积值。
- 使用
- 趋势分析:
- 使用
FIRST_VALUE()和LAST_VALUE()分析数据的起始和结束趋势。
- 使用
- 边界值提取:
- 使用
FIRST_VALUE()提取每组中的第一个值。 - 使用
LAST_VALUE()提取每组中的最后一个值。
- 使用
- 数据标准化:
- 使用
FIRST_VALUE()和LAST_VALUE()将数据标准化到某个范围。
- 使用
4.5、总结
FIRST_VALUE()用于访问窗口框架内的第一个值,适合计算累积值或提取起始值。LAST_VALUE()用于访问窗口框架内的最后一个值,适合分析趋势或提取边界值。- 两者在累积值计算、趋势分析和边界值提取等场景中非常有用。如果需要分析数据的起始或结束趋势,
FIRST_VALUE()和LAST_VALUE()是强大的工具。
五、其他函数
5.1、NTH_VALUE() 函数
NTH_VALUE() 函数用于返回窗口框架内第 N 行的值。
语法:
1 | |
column_name:要访问的列。N:指定要返回的行号(例如,1表示第一行,2表示第二行,依此类推)。PARTITION BY:可选,用于分组。ORDER BY:指定窗口内的排序。ROWS BETWEEN:可选,定义窗口框架的范围(默认为UNBOUNDED PRECEDING AND CURRENT ROW)。
应用场景:
- 提取窗口框架内指定行的值。
- 分析数据集中特定位置的值。
示例:
假设有一组每日销售数据,需要获取每天销售额的第二个值。
1 | |
结果可能如下:
| sales_date | sales_amount | second_sales_amount |
|---|---|---|
| 2023-01-01 | 100 | NULL |
| 2023-01-02 | 150 | 150 |
| 2023-01-03 | 200 | 150 |
| 2023-01-04 | 180 | 150 |
注意:如果窗口框架内没有第 N 行,NTH_VALUE() 会返回 NULL。
5.2、NTILE() 函数
NTILE() 函数用于将数据分区划分为指定数量的桶(bucket),并为每一行分配一个桶号。
语法:
1 | |
N:指定要划分的桶数。PARTITION BY:可选,用于分组。ORDER BY:指定窗口内的排序。
应用场景:
- 将数据划分为百分位数、四分位数等。
- 数据分箱(binning)或分组。
示例:
假设有一组学生成绩数据,需要将成绩分为 4 个等级(四分位数)。
1 | |
结果可能如下:
| student_id | score | quartile |
|---|---|---|
| 1 | 50 | 1 |
| 2 | 60 | 1 |
| 3 | 70 | 2 |
| 4 | 80 | 3 |
| 5 | 90 | 4 |
5.3、NTH_VALUE() 和 NTILE() 的区别
| 函数 | 作用 | 主要用途 |
|---|---|---|
NTH_VALUE() |
返回窗口框架内第 N 行的值。 | 提取指定位置的值。 |
NTILE() |
将数据划分为 N 个桶并分配桶号。 | 数据分箱、百分位数或分组。 |
5.4、常见应用场景
NTH_VALUE() 的应用场景
- 提取特定位置的值:
- 例如,获取某个月的第二高销售额或第三低温度。
- 分析数据集中特定位置的值:
- 例如,找到某个时间段的中间值。
- 数据填充或回填:
- 例如,用第 N 行的值填充缺失值。
NTILE() 的应用场景
- 百分位数计算:
- 例如,将成绩分为 100 个百分位数。
- 数据分箱(Binning):
- 例如,将销售额分为高、中、低三组。
- 分组分析:
- 例如,将用户活跃度分为 5 个等级。
5.5、总结
NTH_VALUE():用于提取窗口框架内第 N 行的值,适合分析特定位置的数据。NTILE():用于将数据划分为指定数量的桶,适合数据分箱、百分位数计算或分组分析。
如果需要提取特定位置的值,使用 NTH_VALUE();如果需要将数据划分为多个组或桶,使用 NTILE()。两者在数据分析和处理中都非常有用,但应用场景不同。
六、开窗聚合函数
以此数据表为例:user
1 | |
6.1、SUM()聚合函数 + 开窗函数
1 | |
结果:每行都是把之前的所有行加起来求和
1 | |
如果不加order by,每一行都会显示与排序字段相同的所有行求和的最终值,可以用来对比个人与总体的数据。【这次以部门分组,这样后面的求和就是此人所在组的总值】
1 | |
结果:
1 | |
还可以指定求和范围:
rows between unbounded preceding and current row 这个就是从起始行到当前行
rows between 3 preceding and current row 这个就是从我的前3行开始到当前行
rows between 3 preceding and 1 following 这个就是从我的前3行开始到我的后一行
rows between current and unbounded following 这个就是从当前行到最后一行
1 | |
结果:
1 | |
6.2、AVG()聚合函数 + 开窗函数
1 | |
结果:每行都是把之前的所有行加起来求平均值
1 | |
6.3、MIN()聚合函数 + 开窗函数
1 | |
结果:每一行都与之前的所有行相比得出最小值
1 | |
6.4、MAX()聚合函数 + 开窗函数
1 | |
结果:每一行都与之前的所有行相比得出最大值
1 | |