SQL窗口函数, since 2022-04-11

(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语句。如果只需要向前或向后计算滑动平均,则删掉BETWEENAND部分。如果计算某一个科目所有人的平均成绩,可将PRECEDINGFOLLOWING前面的数字参数改成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执行序列中的位置

  1. FROM / JOINS
  2. WHERE
  3. GROUP BY
  4. Aggregate Functions
  5. HAVING
  6. Window Functions
  7. SELECT
  8. DISTINCT
  9. UNION / INTERSECT / EXCEPT
  10. ORDER BY
  11. OFFSET
  12. 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容