窗口函数在统计类的需求中很常见,稍微复杂一点的查询需求就有可能用到它,使用窗口函数可以极大的简化我们的 SQL 语句。像 Oracle、SQL Server 这些数据库在较早的版本就支持窗口函数了,MySQL 直到 8.0 版本后才支持它。
一般来说涉及复杂的分组内问题如: 分组排名(取TOPn),分阶段统计(分阶段最大最小平均等)
都可以用到窗口函数
窗口函数需要注意以下两点:
- 窗口分为“静态窗口”和“滑动窗口”,窗口滑动的范围会影响窗口函数的值,这个范围可以根据业务需求自己设置
- 使用了窗口函数
over(partition by id)
时,出来的结果会按这个id字段排在一起(最终是否聚合要看外层有没有group by id)
- 窗口函数分为“聚合”与“非聚合”
1.先看看大概长什么样子,over()是什么含义
可以看到over()里面可以进行partition分区(就是分组的效果)、order by可以排序、后面有个RANGE字段放在第三点讲,这个RANGE或者ROW会影响over的作用效果
select distinct user_id,
first_value(knowledge_name)
over (partition by user_id order by score RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as last,
last_value(knowledge_name)
over (partition by user_id order by score RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as best
from tb_math_knowledge_point
同样的我们还可以对常见的SUM()函数接一个over()
SELECT
*,
SUM(quantity) OVER (PARTITION BY product ORDER BY quantity ROWS CURRENT ROW) AS sum1,
SUM(quantity) OVER (PARTITION BY product ORDER BY quantity RANGE CURRENT ROW) AS sum2,
SUM(quantity) OVER (PARTITION BY product RANGE CURRENT ROW) AS sum3
由此应该可以感觉到,这个over()
就是所谓的窗口,我们可以在over()
中控制滑动窗口的范围,从而取到不同的值
2.以sum() over()来举例
窗口函数的over()中有三个参数:分别是
- partition by
- order by
- frame 滑动窗口的范围
在这一点的demo中会详细解释,本demo参考了https://blog.csdn.net/georgelee954/article/details/105763302?spm=1001.2014.3001.5506
2.1 水果订单表
2.2默认的frame,即默认滑动窗口
在over()中,frame也是有默认值(默认范围)的
根据是否有partition by、order by决定不同的默认值
2.2.1 没有partition也没有order
窗口范围是:所有行,这里即是sum对所有行进行求和
SELECT
*,
SUM(quantity) OVER () AS sum1
FROM sql_store.product_order;
2.2.2只有order 没有partition
窗口范围:第一个分区 到 所在行的分区,这里一般都不会这样单独用,深究意义不大
2.2.3 有partition 无论有没有order
窗口范围:该行的整个分区,注意与上面的区别,如果用partition限定了分区,则扫描到哪一行,就取那行对应的整个分区
2.3上面demo没有指定frame参数
上面的这个demo只在over()中指定了partition和order,因此无法自由控制滑动窗口的范围
3.指定frame滑窗范围
这个fream参数其实叫框架,我这里为了方便理解直接写为了滑窗
3.1滑窗筛选单位
滑窗的筛选单位分为两种:{ROWS
| RANGE
},默认值为RANGE
所谓筛选单位,就是:被视为同一个单位的几个行数据,一起进入窗口函数,并共享最终的唯一结果
我们关于如下脚本,有3种不同的情况:
3.1.1 ROWS:筛选一行
SUM(quantity) OVER (PARTITION BY product ORDER BY quantity ROWS CURRENT ROW) AS sum1
sum1中,使用ROWS作为基本单位,所以对每一行进行单独计算,sum(current row)统计值就是quantity本身
3.1.2 RANGE(有排序):筛选上下连续相同的x对应的值
sum2中,使用RANGE作为基本单位,apple 的第二、第三个订单数量都为10,所以第二、第三行视为一个单位
3.1.3 RANGE(无排序):筛选当前行所在的分区所有行
sum3中,使用RANGE作为基本单位,并且没有ORDER BY,所以每个分区为一个单位
3.2自定义灵活的滑窗范围
个人认为跟上面的3.1滑窗筛选单位ROWS
和RANGE
属于一个包含关系,单位可以选择ROWS行、RANGE范围,都可以筛选一定的范围内,但比较死板,不够灵活,3.2就是这个升级版,足够灵活
- CURRENT ROW :当前行
- UNBOUNDED PRECEDING :当前行上侧所有行
- UNBOUNDED FOLLOWING :当前行下侧所有行
- expr PRECEDING:当前行上侧expr行(expr可以是数字,也可以是表达式)
- expr FOLLOWING:当前行下侧expr行(expr可以是数字,也可以是表达式)
我们可以自由的进行组合,来达到自定义分组(分区)内
滑窗范围的效果
因此我们可以通过这种方式统计:
- 总是关注n几个记录(n日活跃汇总、n场直播观场汇总)
- 按A分组,满足B时C的值(每个部门 A 工资 B 排名前n的员工 C 名字)
- 行在每组的占比(每个员工占自己部门总工资的百分之多少)
滑窗范围有:frame_start 和 frame_between
一般来说用后者,前者是只指定滑窗起点,终点是默认值
后者是起点终点都指定,最为灵活,也最为实用
3.2.1只指定滑窗起点frame_start
仅指定开始行(或区域),则结束范围为默认值,即当前行(或区域)
3.3.2自由度最高的frame_between
SELECT
*,
SUM(quantity) OVER
(PARTITION BY product
ORDER BY quantity
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS sum
FROM sql_store.product_order
- 因为指定了group by,所以按分组求和
- 因为指定了order by,所以有连续值被划为一个单位的可能,所以需要再用frame限定滑窗范围
- 语句设置了frame_start(
UNBOUNDED PRECEDING
)和frame_end(CUREENT ROW
),即框架范围为第一行至当前行,实现了累计求和的效果。
4.实际案例
如果要换成5.7版本的,不用窗口函数,那写起来就很庞大了