窗口函数
本文最后更新于: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 |
|