面试官:你用过什么窗口函数?

作为一个数据工作者,肯定不少被问窗口函数的问题,比如用过什么窗口函数,或者出个题,一般都是需要用到窗口函数才能完成。所以这一篇,专门讲窗口函数,不是做数据开发的也可以看看,当作学习也好,指不定哪天要实现这种逻辑。

什么是窗口函数

相信很多人都比较熟悉 SQL 聚合函数的语法,比如 count(), sum(), max()等,

窗口函数类似聚合函数,不同的是窗口函数不改变原有的行。

窗口函数是数据分析和数据开发必备的技能。

基本语法:

<窗口函数> over (partition by <用于分组的列名> order by <用于排序的列名>)

可能这样的解释还是不明了,没事,往后看,一会你就明白了。

案例

现在先模拟几条数据,假如目前有学生成绩表(stu_scores)如下:

class(班级) id(学号) score(成绩)
1 004 71
2 003 98
1 002 98
2 001 80
2 005 77
1 006 80

语句一:

select *,
    rank①() over②(partition by③ class order by④ score desc) ranking 
from stu_scores;

得到的结果如下:

class(班级) id(学号) score(成绩) ranking
1 002 98 1
1 006 80 2
1 004 71 3
2 003 98 1
2 001 80 2
2 005 77 3

接下来解释一下这段 SQL,这条 SQL 的目的是求每个班级内的成绩排名

① rank() 排序的函数

② over() 指定分析函数工作的数据窗口大小

③ partition by 指定分组字段,这个案例中用 class 作为分组字段, 类似 group by

④ order by 排序,对分组后的结果进行排序

可能有些朋友会问:“这不就是 group by 和 order by 的用法么?不用窗口函数也能实现,为啥要用它?”

这是因为,单纯使用 group by 分组汇总后改变了表的行数,一行只有一个类别;

而使用窗口函数则不改变行数,可以将详细信息也展示出来。

到这,应该大致明白窗口函数的使用场景及如何使用了吧。

为了让大家更好地理解窗口函数,再写几条语句,看看结果是否和你想的一致。

语句二:

select *,
   sum(score) over(order by id) as win_sum,
   count(score) over(order by id) as win_count,
   min(score) over(order by id) as win_min
from stu_scores;

结果:

class id score win_sum win_count win_min
2 001 80 80 1 80
1 002 98 178 2 80
2 003 98 276 3 80
1 004 71 347 4 71
2 005 77 424 5 71
1 006 80 504 6 71

这样的结果是否和你想的一样呢?

由于不加 partition by 因此没有分组,所以从第一行开始开窗做计算。

以 win_sum 为例,第一行成绩相加 80,与第二行相加得 178,再与第三行相加得 276,以此类推。

这样做有什么意义呢?

可以每一行的数据里直观的看到,截止到本行数据,统计数据是多少。

同时可以看出每一行数据,对整体统计数据的影响。

从成绩上可能不太好理解,如果是从生产经营角度,比如对比每月营业额,可以更直观地看出差距。

其它窗口函数及关键字

rank 与 dense_rank

rank(), dense_rank() 都属于排序函数,区别在于有重复数据的时候如何排,看案例就知道

select *,
    rank() over(order by score desc) as ranking,
    dense_rank() over(order by score desc) as dense_ranking
from stu_scores;

结果:

class id score ranking dense_ranking
1 002 98 1 1
2 003 98 1 1
2 001 80 3 2
1 006 80 4 3
1 004 71 5 4
2 005 77 6 5

可以看到,遇到重复排名的时候,rank 是跳跃排序,如果有两个第一,那接下来是第三;

dense_rank()则是连续排序,如果有两个第一时,那接下来是第二。

lead 与 lag

lead(col, n, default_val):用于统计窗口内往下第 n 行值。
第一个参数为列名,第二个参数为往下第 n 行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL。

lag(col,n, default_val):用于统计窗口内往上第n行值,参数和 lead 一样

还是看案例吧,用文字讲确实很难讲清楚

问题:根据班级分组,统计每个班学生的成绩以及小于(大于)等于该学生成绩的上(下)一个学生的成绩:

select *,
    lead(score,1) over(partition by class order by score) as lead,
    lag(score,1) over(partition by class order by score) as lag
from stu_scores;

结果:

class id score lead lag
1 004 71 80 null
1 006 80 98 71
1 002 98 null 80
2 005 77 80 null
2 001 80 98 77
2 003 98 null 80

可以看到,第二行 lead 的结果是第三行的成绩,lag 的结果是第一行的成绩,没有的则为 null

last_value 与 first_value

这两个比较简单,顾名思义,分别表示取窗口内的最后一个值和第一条数据,但是先看看例子

select *,
    first_value(score) over(partition by class order by score) as first,
    last_value(score) over(partition by class order by score) as last
from stu_scores;

结果:

class id score first last
1 004 71 71 71
1 006 80 71 80
1 002 98 71 98
2 005 77 77 77
2 001 80 77 80
2 003 98 77 98

从结果看,first_value 的结果很合理,是每个分区的第一个数据;

但 last_value 的结果好像不符合期望,这个和我接下来要说的几个关键字有关。

UNBOUNDED、PRECEDING、FOLLOWING、CURRENT ROW

先粗略地解释一下这些关键字:

CURRENT ROW:当前行

n PRECEDING:往前 n 行数据

n FOLLOWING:往后 n 行数据

UNBOUNDED:起点

  • UNBOUNDED PRECEDING 表示从前面的起点,

  • UNBOUNDED FOLLOWING 表示到后面的终点

老规矩,先看例子

SELECT *,
  last_value(score) over(PARTITION BY class ORDER BY score) last1,
  last_value(score) over(PARTITION BY class ORDER BY score RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT row) last2,
  last_value(score) over(PARTITION BY class ORDER BY score RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED following) last3,
  last_value(score) over(PARTITION BY class ORDER BY score RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING) last4,
  last_value(score) over(PARTITION BY class ORDER BY score ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) last5
FROM stu_scores;

结果:

class id score last1 last2 last3 last4 last5
1 004 71 71 71 98 71 80
1 006 80 80 80 98 80 98
1 002 98 98 98 98 98 98
2 005 77 77 77 98 80 80
2 001 80 80 80 98 80 98
2 003 98 98 98 98 98 98

用的都是 last_value 结果还不一样,为什么呢?

这是因为,last_value 默认的窗口是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

表示当前行永远是最后一个值,因此 last1 和 last2 的结果是一样的。

如果要获取每个分组的最后一个值,

则需改成 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING

表示从最前一行作为起点,最后一行为终点,就是 last3 的结果(由于两个分组排序后最后一个数都是 98,看不出区别,大家可以去验证一下)

至于 last4 和 last5 的区别,则是 RANGE 和 ROWS 的区别:

RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING:

表示当前行的值分别减 3 和 加 3,以第 4 行为例,原来的 score 是 77,各加减 3,则是 74 到 80 的范围

80 刚好是下一行的值,因此它的结果为 80,其它行由于加减 3 后没有对应的值,因此为自身。

ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:

表示当前行分别往前往后减一行,记住,rows 和 range 的区别就在于是当前行数还是当前行的值。

因此在同一个分组内,第一行 last5 的值为下一行的值,第二行为下一行的值,以此类推,第二个分组也一样。

总结

到这,窗口函数的内容就基本上都讲完了。这些可以说是数据分析和数据开发必备的技能,因此必须要熟练。

至于如何才能熟练,还需要多实践。

码字不易,如果觉得不错,麻烦动动小手点个赞,谢谢!

持续关注不迷路,转载请注明出处!—— 大数据的奇妙冒险

#大数据开发##数据分析师##数据库相关面试常考题汇总#
大数据从入门到放弃 文章被收录于专栏

写点大数据相关的内容,一起交流进步

全部评论
mark
点赞 回复 分享
发布于 2023-10-24 15:21 河北
楼主说的很清楚,点赞
点赞 回复 分享
发布于 2022-11-14 09:06 广东
😃 1.窗口函数+聚合函数 sum()over([partition by] [order by ] [ rows between and]) 2.窗口函数+排序  rank(),dense_rank(),row_number()
点赞 回复 分享
发布于 2022-08-06 23:55
mark
点赞 回复 分享
发布于 2022-08-01 01:17
mark
点赞 回复 分享
发布于 2022-07-17 12:50
mark
点赞 回复 分享
发布于 2022-07-07 22:24
楼主厉害了,佩服
点赞 回复 分享
发布于 2022-06-26 17:54

相关推荐

bg:双非本,一段中小厂6个月测开实习今天发这个帖子主要是想聊一聊我秋招以来的一个发展我是在8月底辞职,打算秋招,可是看网上都说金九银十就想着自己就是一个普通本科生,现在九月份都是一些大神在争抢,所以9月份基本上没投,等到了10月份才开始秋招,可是这个时间好像已经有些晚了,今年秋招开启的格外早,提前到了7,8月份,我十月才开始,官网投了很多公司,没有任何一个面试机会,这个情况一直到了十月底才有了第一个面试,当时没有面试经验,所以不出意外的挂了后续就是漫长的投递,但是毫无例外没有面试,没有办法我只能另辟蹊径开始在BOSS上边投递,然后顺便也根据BOSS上边这个公司名称去浏览器搜索看看有没有官网投递渠道,毕竟官网上投递后还是可以第一时间被HR看到的,然后一直不停投递,一开始第一个星期基本上都是投的正式秋招岗位到了第二个星期才开始实习和正式一起投,到十一月底的时候已经沟通了700➕才有一共1个正式的,5个要提前实习的,3个实习的面试,最后结果是过了1个要提前实习的和2个实习的每次面试我都会复盘,发现这些小公司面试官问的五花八门,有的专问基础,有的专问项目,有的啥都问,不过自己也是看出来了一下门道,就是小公司不像大公司面试官那样能力比较强基本上你简历上边的他都会,然后会根据简历来问,小公司面试官他们更多的是看自己会什么,然后看看你简历上边哪些他也是会的然后来问,经过不断的复盘加上背各种各样面试题,到了11月底12月初才有了1个要提前实习的offer还有2个实习的offer,而且薪资待遇对我来说已经很可观了可是啊,人总是这样得了千钱想万钱,我又开始不满现状,但是此时的我面试能力经过这么多面试和复盘已经很强了,然后在十二月份运气爆棚,被极兔和小鹏补录捞起来面试,还有个百度测开的实习面试,这个时候因为有了offer所以感觉有了底气,面试也很自信,最后结果是全部都过了那个时候我感觉自己真的很厉害,我问了极兔那边的HR像我这样的双非本收到offer的在极兔有多少?他告诉我产研岗90%都是硕士,10%里边基本上都是211,985,想我这样的很少很少,那一刻感觉自己超级牛逼,小鹏就更不用说了,最后也是不出意外选择了小鹏所以我就我个人经历想对和我学历履历差不多的牛友一些建议第一:秋招一定要趁早,真到了9,10月,那个时候可能你投的结果可能还不如7,8,11月,第二:最好先拿小公司实习或者正式练练手,提升一下面试能力,我个人觉得因为小公司问的五花八门所以你会更加横向去提升自己能力,而且大公司其实面试没有那么难,除了一些非常卷的岗位,公司大神比较多会问的很难,一般好点的公司都不会问的那么难,他们也知道都是应届生不会要求那么高第三:当有一定能力后,就是坚持了,对于我们这样的学历,没有特别强的履历情况下,就是要抓住提前批和补录的机会,这个时候各方面不会卡的很严,是我们很好很好的一个机会第四:就是运气也是很重要的一部分,不过这个很难去说什么最后祝各位牛友都能收获自己满意的offer😁😁😁
秋招,不懂就问
点赞 评论 收藏
分享
2025-12-12 19:01
南京航空航天大学 C++
秋招没咋投,准备&nbsp;wxg&nbsp;转正之后摆烂了。结果不堪字节&nbsp;HR&nbsp;的骚扰还是面了一下字节。之前想去字节的时候怎么面都挂。现在想着随便面一下结果三面技术面都意外顺利还有加面。十月中旬字节发了意向,wxg&nbsp;转正结果无响应。十月底字节拉了保温群,wxg&nbsp;口头通过,系统显示考核中。十一月初和字节&nbsp;ld&nbsp;交流之后得知&nbsp;base&nbsp;居然能选海外,甚至能小&nbsp;wlb&nbsp;一下,wxg&nbsp;无响应无人联系。十一月中旬把字节&nbsp;base&nbsp;转到了海外,wxg&nbsp;流程灰了,一问超时忘处理了,过两天又变考核中了。十一月下旬字节换了海外&nbsp;HR&nbsp;对接,问了期望薪资,wxg&nbsp;考核终于显示通过,无&nbsp;HR&nbsp;保温,无其他保温。十一月底给字节报了个天价,想吓吓他们,同时告诉微信字节要开了,微信无响应。同样十一月底字节&nbsp;HR&nbsp;告诉我确实给不到那么高,但是能拿期权补上,问能不能接受。微信无响应。同样十一月底字节&nbsp;HR&nbsp;告知了具体方案,符合预期。&nbsp;微信无响应。十二月上旬催&nbsp;wxg&nbsp;不开我就盲拒了,wxg&nbsp;HR&nbsp;火急火燎的打电话问情况,问期望。我给了一个不算夸张的总包数字,因为今年市场在涨,过了三天还不联系我,我再催,约时间下午打电话,非得在我给出的数字上压下去几万,微信又不差这点,为什么不能满足我,让我没有拒绝的理由呢?一番纠结抗争,求稳还是追求挑战,最终选择接受迎接新的挑战,因为堂吉诃德永远不会停下脚步!回想起来,在&nbsp;wxg&nbsp;谈薪的阶段,我认为并没有给予我一定的重视,即使&nbsp;HR&nbsp;表示我在实习期间的表现和之前的面评都很靠前。也没有感觉到想要争取我,虽然我表示拒了&nbsp;offer&nbsp;之后要给我加面委定&nbsp;t6&nbsp;再涨,但我三个月没面试让我面面委那就是白给,还是算了。有缘再见了我亲爱的&nbsp;wxg,再见了曾经的梦中情厂,再见亲爱的&nbsp;mt,再见亲爱的朋友们。也再见,北京的一切。我想润了。秋招结束,卸载牛客,下一个三年,下一个五年,下一个十年后再来看看。
面试中的大熊猫爱吃薯...:我嫉妒得狗眼通红
点赞 评论 收藏
分享
评论
72
235
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务