窗口函数再深入,结合案例详解
MySQL窗口函数列表
名称 | 描述 |
---|---|
序号函数 | |
ROW_NUMBER |
为其分区中的每一行分配一个连续整数 |
DENSE_RANK |
根据ORDER BY 子句为其分区中的每一行分配一个排名。 它为具有相同值的行分配相同的排名。 如果两行或更多行具有相同的等级,则排序值序列中将没有间隙。 |
RANK |
与DENSE_RANK() 函数类似,只是当两行或更多行具有相同的排名时,排序值序列中存在间隙。 |
分布函数 | |
PERCENT_RANK |
计算分区或结果集中行的百分位数 |
CUME_DIST |
计算一组值中值的累计分布 |
前后函数 | |
LAG |
返回分区中当前行之前的第N行的值。 如果不存在前一行,则返回NULL。 |
LEAD |
返回分区中当前行之后的第N行的值。 如果不存在后续行,则返回NULL。 |
头尾函数 | |
FIRST_VALUE |
返回指定表达式相对于窗口框架中第一行的值。 |
LAST_VALUE |
返回指定表达式相对于窗口框架中最后一行的值。 |
聚合函数 | |
SUM,AVG,COUNT,MAX |
|
MEDIAN |
中位数 |
STDDEV |
总体标准差 |
STDDEV_SAMP |
样本标准差 |
其他函数 | |
NTH_VALUE |
返回窗口框架第N行的参数值 |
NTILE |
将每个窗口分区的行分配到指定数量的已排名组中。 |
窗口函数详解-结合案例
以订单表orders
为例,来介绍每个函数的使用方法。表中各字段含义按顺序分别为order_id订单号
、user_no用户id
、amount订单金额
、create_date订单创建日期
。
1 | CREATE TABLE `orders` ( |
序号函数
- 序号函数:
row_number()
,rank()
,dense_rank()
- 用途:
显示分区中的当前行号
- 使用场景: 查询每个用户订单金额最高的前三个订单
- 用途:
1 | SELECT * FROM ( |
此时可以使用ROW_NUMBER()
函数按照用户进行分组并按照订单金额进行由大到小排序,最后查找每组中序号<=3的记录。
1 | row_num order_id user_no amount create_date |
对于用户2
的订单,大家发现订单金额为800的有两条,序号随机排了1和2,但很多情况下二者应该是并列第一,而订单为600的序号则可能是第二名,也可能为第三名,这时候,row_number
就不能满足需求,需要rank
和dense_rank
出场。
1 | SELECT * FROM |
1 | row_num1 row_num2 row_num3 order_id user_no amount create_date |
row_number()
在amount都是800的两条记录上随机排序,但序号按照1、2递增,后面amount为600的的序号继续递增为3,中间不会产生序号间隙;rank()
/dense_rank()
则把amount为800的两条记录序号都设置为1,但后续amount为600的需要则分别设置为3(rank)和2(dense_rank)。即rank()会产生序号相同的记录,同时可能产生序号间隙;而dense_rank()也会产生序号相同的记录,但不会产生序号间隙。
分布函数
- 分布函数:
percent_rank()
,cume_dist()
- 用途: 和之前的RANK()函数相关,每行按照如下公式进行计算:
(rank - 1) / (rows - 1)
- 应用场景: 大于等于当前订单金额的订单比例有多少。
- 用途: 和之前的RANK()函数相关,每行按照如下公式进行计算:
1 | SELECT * FROM |
1 | row_num percent order_id user_no amount |
前后函数
- 前后函数:
lead()
,lag()
- 用途: 分区中位于当前n行(lead) / 后n行(lag)的记录值
- 使用场景: 查询上一个订单距离当前订单的时间间隔
1 | SELECT |
1 | order_id user_no amount create_date last_date diff |
内层SQL先通过lag函数得到上一次订单的日期,外层SQL再将本次订单和上次订单日期做差得到时间间隔diff。
头尾函数
- 头尾函数:
first_val(expr)
,last_val(expr)
- 用途: 得到分区中的第一个/最后一个指定参数的值
- 使用场景: 查询截止到当前订单, 按照日期顺序第一个订单和最后一个订单的订单金额
1 | SELECT * FROM |
1 | order_id user_no amount create_date first_amount last_amount |
结果和预期一致,比如order_id为4的记录,first_amount
和last_amount
分别记录了用户‘1’截止到时间2018-01-03 00:00:00为止,第一条订单金额100和最后一条订单金额800,注意这里是按时间排序的最早订单和最晚订单,并不是最小金额和最大金额订单。
其他函数
- 其他函数:
nth_value(expr,n)
,nfile(n)
nth_value(expr,n)
- 用途: 返回窗口中第N个expr的值,expr可以是表达式,也可以是列名
- 应用场景: 每个用户订单显示本用户金额第二和第三的订单金额
1 | SELECT * FROM |
1 | order_id user_no amount create_date second_amount third_amount |
聚合函数
- 用途: 在窗口中每条记录动态应用聚合函数(sum/avg/max/min/count),可以动态计算在指定的窗口内的各种聚合函数值
- 使用场景: 每个用户按照订单id,截止到当前的累计订单金额/平均订单金额/最大订单金额/最小订单金额/订单数是多少?
1 | SELECT * FROM |
1 | order_id user_no amount create_date sum1 avg1 max1 min1 count1 |
总结
窗口函数语法
<窗口函数> over (partition by <用于分组的列名> order by <用于排序的列名>)
- <窗口函数>的位置,可以放以下两种函数:
- 专用的窗口函数, 比如
rank()
,dense_rank()
,row_number()
等 - 聚合函数, 如
sum
,avg
,count
,max
,min
等
- 专用的窗口函数, 比如
窗口函数的功能
- 同时具有
分组(partition by)
和排序(order by)
的功能
- 同时具有
- 不减少原表的行数, 所以经常用来每组内排名