(2022.04.11 Mon)
窗口函数
窗口函数是作用于数据关系中的一系列记录的SQL函数,这些记录称为窗口(window/window frame)。窗口是被查询的行(row),比如当前行之前的所有行,当前行到后面的三行,之类。
窗口函数功能上类似于聚合函数(aggregate function),都是用于计算一组记录的统计结果。不同于聚合函数,窗口函数不会聚合行数据(collapse row),而保留每行数据的完整信息。
窗口函数原则上只能使用在SELECT
字句中。
基本的调用格式
<window_function> OVER
([PARTITION BY <group_column>]
ORDER BY <order_column>
[ROWS BETWEEN <m> PRECEDING AND <n> FOLLOWING])
- window_function:指定的窗口函数
- OVER:用于定义/指定窗口内的数据记录/行,如果不指定后面跟着的字句,如PARTITION/ORDER/ROWS,即
OVER()
,则窗口函数作用于整个窗口 - PARTITION BY部分:可选,定义窗口内的组(partition),根据指定的字段<group_column>对数据进行分组,不指定PARTITION就不分组
- group_column:用于分组的列
- order_column:用于排序的列
- ROWS/RANGE BETWEEN部分:用于指定窗口范围,可选
- m:用户指定的当前行前面的行数,可写为UNBOUNDED
- n:用户指定的当前行后面的行数,也可写为UNBOUNDED。
典型问题
窗口函数经常用于解决的问题包括“既分组又排序”,top N问题(找出一个组织内部的top n员工),最值问题等。
分类
窗口函数分为四类
- 聚合函数aggregate function
- 排序函数ranking function
- 分析函数analytic function
- 分布函数distribution function
聚合函数
包括AVG, COUNT, MAX, MIN和SUM。需要传递变量名作为参数。
计算某个公司的若干员工的总工资,关系表示为salary(eid, ename, department, gender, salary)。
select *,
sum(salary) over(order by salary) ss
from test_data.salary;
返回结果为
eid | ename | gender | department | salary | ss |
---|---|---|---|---|---|
1003 | 'lucy' | 'f' | 'operation' | 600 | 600 |
1002 | 'josh' | 'm' | 'design' | 800 | 1400 |
1005 | 'alex' | 'm' | 'product' | 900 | 2300 |
1001 | 'john' | 'm' | 'sales' | 1000 | 3300 |
1004 | 'kate' | 'f' | 'sales' | 1200 | 4500 |
SUM配order by用于计算累加和。
计算各部门的最高工资,注意这里就不需要加order by部分。一旦加入则各自计算每个人的而非每个部门。
select *,
max(salary) over(partition by department) ss
from test_data.salary;
eid | ename | gender | department | salary | ss |
---|---|---|---|---|---|
1002 | 'josh' | 'm' | 'design' | 800 | 800 |
1003 | 'lucy' | 'f' | 'operation' | 600 | 600 |
1005 | 'alex' | 'm' | 'product' | 900 | 900 |
1001 | 'john' | 'm' | 'sales' | 1000 | 1200 |
1004 | 'kate' | 'f' | 'sales' | 1200 | 1200 |
计算平均工资,按性别分类。注意这里将使用order by指令,会返回前n个员工的平均工资,而非某个性别全部员工的平均工资。
select *,
avg(salary) over(partition by gender order by salary) ss
from test_data.salary;
eid | ename | gender | department | salary | ss |
---|---|---|---|---|---|
1003 | 'lucy' | 'f' | 'operation' | 600 | 600 |
1004 | 'kate' | 'f' | 'sales' | 1200 | 900 |
1002 | 'josh' | 'm' | 'design' | 800 | 800 |
1005 | 'alex' | 'm' | 'product' | 900 | 850 |
1001 | 'john' | 'm' | 'sales' | 1000 | 900 |
(2022.04.12 Tues)
计算所有学生的各科滑动平均成绩,也就是先做科目排名,再计算前后各1个人累计三个人的平均成绩,保留两位小数。该方法也可用于计算公司员工业绩排名和平均业绩。关系score_board(stu_id, stu_name, subjects, stu_score)。
SELECT *,
ROUND(AVG(score) OVER(PARTITION BY subjects
ORDER BY score DESC ROWS
BETWEEN 1 PRECEDING AND 1 FOLLOWING), 2) AS avg_score
FROM score_board;
stu_id | stu_name | subjects | scores | avg_score |
---|---|---|---|---|
1001 | 'zhangsan' | 'chinese' | 81 | 81 |
1003 | 'wangwu' | 'chinese' | 81 | 79.33 |
1002 | 'lisi' | 'chinese' | 76 | 78.5 |
1003 | 'wangwu' | 'english' | 90 | 89 |
1002 | 'lisi' | 'english' | 88 | 81 |
1001 | 'zhangsan' | 'english' | 65 | 76.5 |
1003 | 'wangwu' | 'math' | 100 | 95 |
1002 | 'lisi' | 'math' | 90 | 88.33 |
1001 | 'zhangsan' | 'math' | 75 | 82.5 |
如果不需要分partition,则删掉PARTITION BY
语句。如果只需要向前或向后计算滑动平均,则删掉BETWEEN
和AND
部分。如果计算某一个科目所有人的平均成绩,可将PRECEDING
和FOLLOWING
前面的数字参数改成UNBOUNDED
,或者直接删掉ROWS
开始的部分,得到的就是科目的平均成绩。
SELECT *,
ROUND(AVG(score) OVER(PARTITION BY subjects
ORDER BY score DESC ROWS
BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), 2) AS avg_score
FROM score_board;
SELECT *,
ROUND(AVG(score) OVER(PARTITION BY subjects
ORDER BY score DESC), 2) AS avg_score
FROM score_board;
有关系score_board(stu_id, stu_name, subjects, stu_score),找出成绩低于该科目平均成绩的同学信息,之后开除。
找出:
select distinct(stu_id)
from
(select *,
avg(score) OVER(Partition by subjects) as avg_score
from score_board) a
where score < a.avg_score;
开除:
DELETE FROM score_board where stu_id IN
(
select distinct(stu_id)
from
(select *,
avg(score) OVER(Partition by subjects) as avg_score
from score_board) a
where score < a.avg_score;
);
也可通过子查询方式找出符合条件的结果
select *
from score_board a
where score < (select avg(score) from score_board b
where a.subjects = b.subjects);
排序函数
包括rank, row_number, dense_rank。这三个函数都用于排序,但在出现并列的情况下,排序结果不同。排序函数在使用时不需要传递参数,即rank()/row_number()/dense_rank()。
- rank: 如果排序中有并列情况,如三个并列第一,则下一位将是第四,即1,1,1,4
- row_number: 即便出现并列的情况,返回的仍然是所在行的数字,按自然数顺序排列,1,2,3,4之类
- dense_rank: 出现并列的情况,如三个并列第一,则下一位是第二,即1,1,1,2。
有关系score_board(stu_id, stu_name, subjects, stu_score),找出每科成绩最高的信息。
SELECT * FROM
(SELECT *,
RANK() OVER(PARTITION BY subjects ORDER BY stu_score DESC) AS ranking
FROM score_board) a
WHERE a.ranking = 1;
分析函数
包括lead, lag, first_value,用于获取当前窗口数据中的其他行。lead用于返回当前行后面的值,lag返回当前行前面的值。调用方式是lead/lag(col, n, default_value),其中的n是指定的行数,比如n=1,则返回当前行前面/后面1行的值,default_value指如果返回是Null则赋予default_value。first_value仅需要提供字段名,即first_value(col)。返回最大/最小/最低/最高的值问题都可以考虑用FIRST_VALUE来解决,注意配合ORDER BY的DESC/ASC。
下面是lead的调用方式和结果。
SELECT *,
LEAD(score,1) OVER(PARTITION BY stu_name ORDER BY score desc) AS sr
FROM test_data.stu_score;
返回结果如下
stu_id | stu_name | subjects | score | sr |
---|---|---|---|---|
1002 | 'lisi' | 'math' | 90 | 88 |
1002 | 'lisi' | 'english' | 88 | 76 |
1002 | 'lisi' | 'chinese' | 76 | NULL |
1003 | 'wangwu' | 'math' | 100 | 90 |
1003 | 'wangwu' | 'english' | 90 | 81 |
1003 | 'wangwu' | 'chinese' | 81 | NULL |
lag的调用结果,返回的是当前subject的score值的前一个值,如果前一个值为Null(即当前值即第一个值),则返回default_value,本例中为-1
SELECT *,
lag(score, 1, -1) over(partition by subjects order by score desc) as lag_score
from score_board;
stu_id | stu_name | subjects | score | lag_score |
---|---|---|---|---|
1001 | 'zhangsan' | 'chinese' | 81 | -1 |
1003 | 'wangwu' | 'chinese' | 81 | 81 |
1002 | 'lisi' | 'chinese' | 76 | 81 |
1003 | 'wangwu' | 'english' | 90 | -1 |
1002 | 'lisi' | 'english' | 88 | 90 |
1001 | 'zhangsan' | 'english' | 65 | 88 |
1003 | 'wangwu' | 'math' | 100 | -1 |
1002 | 'lisi' | 'math' | 90 | 100 |
1001 | 'zhangsan' | 'math' | 75 | 90 |
分布函数
包括PERCENT_RANK和CUME_DIST,分别用于获得分位点排序(percentile ranking)和累计分布(cumulative distribution)。
窗口函数在SQL执行序列中的位置
- FROM / JOINS
- WHERE
- GROUP BY
- Aggregate Functions
- HAVING
- Window Functions
- SELECT
- DISTINCT
- UNION / INTERSECT / EXCEPT
- ORDER BY
- OFFSET
- LIMIT / FETCH / TOP
一般来说窗口函数不会出现在WHERE语句中。
Reference
1 https冒号//learnsql点com/blog/sql-window-functions-interview-questions/
2 https冒号//www点educba点com/sql-window-functions/
3 https冒号//zhuanlan点zhihu点com/p/390381181
4 https冒号//zhuanlan点zhihu点com/p/92654574
网友评论