窗口函数的一些理解
Tips:
MySQL 8.0以下不支持窗口函数,非要使用的话参见:https://stackoverflow.com/questions/3333665/rank-function-in-mysql
简介
窗口函数,别名:开窗函数、Window函数。又称分析函数
常见使用场景为求排名、TopN、累加等涉及到对数据做二次处理且依赖上下文的环境的计算场景
历史:2003年被加入到SQL标准中,2011年的修订中添加了一些增强功能。
语法
function([expr]) over (
[partition by partition_expr]
[order by order_expr]
[frame]
)
分区:分区由 partition by 子句定义。如果没有指定 partition by 子句,则整个查询与分析结果集作为一个窗口分区。
排序:排序由 order by 子句定义
框架(窗口):框架在分区内对行进一步限制。框架元素不适用于排名函数。使用 ROWS BETWEEN start AND end 来定义
函数列表
专用窗口函数
- row_number()
- 取行号
- rank()
- 取排名 排名相同则值一样 但会占用后续排名的位置 比如 1、1、1、4
- dense_rank()
- 取排名 排名相同则值一样 但不会占用后续排名的位置 比如 1、1、1、2 与rank()的区别见:https://zhuanlan.zhihu.com/p/92654574
- percent_rank()
- 计算分区内排名小于当前行的数据条数占总条数(不包含排名最高的行)的比例
- cume_dist()
- 计算分区内排名小于等于当前行的数据条数占总条数的比例
- lag(expr [, N[, default]])
- 返回分区内排名小于当前行的第前N行位置的值 expr 代表可以不仅仅可以取字段,也可以在取的同时对字段做操作 N默认为1 default默认为null
- lead(expr [, N[, default]])
- 与lag相反,取分区内排名大于当前行的第前N行位置的值
- ntitle(N)
- 将分区内的数据分为N组,返回值为组序号(最小为1)
- first_value(expr)
- 取当前窗口范围内的第一行,lag和leag是相对位置, first_value是绝对位置,
- last_value(expr)
- 取当前窗口范围内的最后一行
- nth_value(expr, N)
- 取当前窗口范围内的第N行
可用于窗口函数的聚合函数
- avg
- count
- sum
- min
- max
- bit_and
- stddev 标准差
- variance 方差
- ...
窗口函数与聚合函数的对比
- 两者在执行前都会对数据进行分区,窗口函数使用 PARTITION BY,聚合函数使用 GROUP BY。如下图,数据被分为两个区
- 窗口函数相比于聚合函数多了一个窗口的概念,即分区之后又进行了一次虚拟的切分,把一个分区内的数据分成了多个窗口,并且多个窗口间数据是有重复的。
场景1:窗口宽度不设置,则默认为从第一行到当前行
SELECT RANK() OVER ( PARTITION BY `color` ORDER BY `id` # Frame 默认为空 等价于 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) FROM color_table
场景2:窗口宽度为前后各一行
SELECT RANK() OVER ( PARTITION BY `color` ORDER BY `id` ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ) FROM color_table
场景3:窗口宽度为3包含前两行
SELECT RANK() OVER ( PARTITION BY `color` ORDER BY `id` ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) FROM color_table
- 窗口函数的每次操作数据范围为窗口数据的行数,移动步长为1。聚合函数的每次操作数据范围为分区数据的行数,移动步长为分区行数。
- 聚合函数也可以在窗口函数中使用,但作用范围缩小为对每个窗口内的数据进行聚合操作
窗口范围定义要点总结
ROWS 和 RANGE 的区别
- ROWS:框架由开始行和结束行的位置来定义,偏移量是行号与当前行号的差异。这个比较常用。
- RANGE:框架由值范围内的行定义,偏移量是行值与当前行值的差异。这个不太常用。
ORDER BY 对窗口范围的影响
- 没有ORDER BY, 则窗口范围是整个分区。因为没有排序,所以每一行都是对等的。
- 有ORDER BY,则窗口范围为分区内第一行到当前行。
窗口范围定义实例
- 前后各N行
- ROWS BETWEEN N PRECEDING AND N FOLLOWING
- 前N行到前M行
- ROWS BETWEEN N PRECEDING AND M PRECEDING
- 第一行到后N行
- ROWS BETWEEN UNBOUNDED PRECEDING AND N FOLLOWING
- 前N行到最后一行
- ROWS BETWEEN N PRECEDING AND UNBOUNDED FOLLOWING
使用窗口函数的一些场景
默认数据表为 score
一、求当前行值的占比
利用了不排序的窗口特性
SELECT *, sum( `value` ) over w AS `sum`, `value` / sum( `value` ) over w AS `percent` FROM scores WINDOW w AS ()
二、累乘
使用对数+累加实现
SELECT *, power(2, sum(log2( `value` )) OVER ( ORDER BY `value` ) ) cum_multi FROM scores
参考文档:
mysql8.0 窗口函数文档
StackOverflow上关于MySQL如何实现rank()的说明
通俗易懂的学会:SQL窗口函数
维基百科:SQL
数据分析|SQL窗口函数最全使用指南
窗口函数