窗口函数(window function)是mysql8.0 以后才引用的新函数,用于计算累加、排名、序号类的问题。
最经典的就是row_number() 函数,用于既定规则的排序。
什么是窗口函数
窗口函数的本质:
- 即想聚合计算,又不想丢失原始行
在窗口函数中:
- 保留每一行
- 还能看到:
- 排名
- 累加
- 平均值
- 前一行
- 后一行
- 当前行附近的数据
这就是:
在一组行上进行计算,但又不折叠行
可以和分组函数进行区别 Group by
题目解析
在力扣 1204. 最后一个能进入巴士的人
题目中,使用turn进行排序,标记进入巴士的顺序,并且标定了最大限重1000KG ,这是典型的累加窗口函数的使用场景,因此,题解sql为:
with a as(
select person_name, weight, turn,
sum(weight) over(order by turn) sum_weight
from queue
)
select person_name
from a
where sum_weight <= 1000
order by turn desc
limit 1数据(简化数据 Queue 表按 turn 列由小到大排序。)
| Turn | ID | Name | Weight | Total Weight | |
|---|---|---|---|---|---|
| 1 | 5 | Alice | 250 | 250 | |
| 2 | 3 | Alex | 350 | 600 | |
| 3 | 6 | John Cena | 400 | 1000 | 最后一个上巴士 |
| 4 | 2 | Marie | 200 | 1200 | 无法上巴士 |
| 5 | 4 | Bob | 175 | ___ | |
| 6 | 1 | Winston | 500 | ___ |
sql执行顺序
其中,with表达式中,对数据进行了累加求和:
sum(weight) over(order by turn)
排序列,turn:对turn升序排序
窗口以Turn计数
-
第一行:
窗口:
【1】
计算:
250 -
第二行:
窗口:
【1,2】
计算:
250 + 350 = 600 -
第三行
窗口:
【1,2,3】
计算:
250 + 350 + 400 = 1000
并最终,按照sum(weight) over(order by turn) sum_weight 语法:
| person_name | weight | turn | sum_weight |
|---|---|---|---|
| Alice | 250 | 1 | 250 |
| Alex | 350 | 2 | 600 |
| John Cena | 400 | 3 | 1000 |
| Marie | 200 | 4 | 1200 |
| Bob | 175 | 5 | 1375 |
| Winston | 500 | 6 | 1875 |
实现数据的累加。
理解窗口函数中的窗口
直接从语法讲解,窗口函数仿佛晦涩一些,我之前都是死记硬背语法,聚合函数后接over(),然后接PARTITION BY分区啊巴拉巴拉的。
难以理解,不用就会忘掉,实际没有理解窗口函数的精髓。
一言以蔽之,窗口函数,解决的问题在于,让当前行,可以看到其他行。
理解了数据库当前到底在观察那一批数据,就可以明白窗口函数的用法以及概念。
函数的典型结构为:
SELECT
[窗口函数] OVER (
[PARTITION BY 分区字段]
[ORDER BY 排序字段]
[窗口帧定义]
) AS 列名
FROM 表;我们来逐步拆解:
OVER 作用
作用:定义当前行的观察范围
可以理解为:当前行能看到谁?
例如:SUM(salary) OVER ()
意思为:当前行可以看到全表
例如,表:
| name | salary |
|---|---|
| 张三 | 3000 |
| 李四 | 5000 |
| 王五 | 7000 |
sql:
SELECT
name,
salary,
SUM(salary) OVER ()
FROM emp;
结果:
| name | salary | sum |
|---|---|---|
| 张三 | 3000 | 15000 |
| 李四 | 5000 | 15000 |
| 王五 | 7000 | 15000 |
其中,sum的结果每一行都是 15000 因为,当前的每一行都能看到全表
PARTITION BY 作用
partition 的本质:把大窗口切成小窗口
可以理解为:分组观察
效果类似与分组GROUP BY ,但是结果被压缩,指示分组观察。
例如,表:
| name | dept | salary |
|---|---|---|
| 张三 | 技术 | 3000 |
| 李四 | 技术 | 5000 |
| 王五 | 财务 | 7000 |
sql:
SELECT
name,
dept,
salary,
SUM(salary) OVER (
PARTITION BY dept
) AS dept_sum
FROM emp;数据库的执行:
对于 张三
PARTITION BY dept意味着:
张三只能看到“技术部”那么在 张三 数据行 ,张三 能OVER观察到:
【张三,李四】所以,
SUM:3000 + 5000 = 8000
对于 王五 ,只能OVER观察到 财务部,
所以dept_sum数据为7000
最终结果:
| name | dept | salary | dept_sum |
|---|---|---|---|
| 张三 | 技术 | 3000 | 8000 |
| 李四 | 技术 | 5000 | 8000 |
| 王五 | 财务 | 7000 | 7000 |
注意,PARTITION BY 不是 GROUP BY
虽然都是分组,但PARTITION BY 是划分观察范围;GROUP BY会折叠压缩行
ORDER BY 作用
这是窗口内部的顺序
这对于窗口函数非常重要,定义了排序的规则、累加的反向等。
例如:
SUM(salary) OVER (
ORDER BY id
)意为
当前行按照 id 顺序逐步观察
为什么ORDER BY 能实现累计?
直接上例子,表:
| id | salary |
|---|---|
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
sql:
SUM(salary) OVER (
ORDER BY id
)
- 对于第一行
窗口:100
SUM:100 - 第二行
窗口:100 200
SUM:300 - 第三行
窗口:100 200 300
SUM:600
于是结果:
| id | salary | sum |
|---|---|---|
| 1 | 100 | 100 |
| 2 | 200 | 300 |
| 3 | 300 | 600 |
为什么会越来越多?实现了累加?
因为:ORDER BY 默认生成:从第一行 到 当前行
这就是窗口帧 窗口帧(Window Frame)
窗口帧
汇总来看整个窗口函数:
PARTITION BY:决定能看到那些人;
ORDER BY:决定按什么顺序看;
窗口帧:决定具体看到第几行.
更加形象的例子在于:
- PARTITION BY = 班级
- ORDER BY = 学号排序
- 窗口帧 = 你视线能看到前后几个人
以上就是我自己对于窗口函数的理解,比较浅显,如有错误或者理解偏差,欢迎指正