黑马都没教过的MySQL窗口函数,我1篇博客就给你讲得比黑马更清楚!

什么是MySQL窗口函数?

MySQL窗口函数,它跟函数没有一点关系,就是指在单个或多个指定窗口范围内执行函数 ^1^,而窗口呢,可以理解为一个个的组。

MySQL窗口函数的写法

MySQL的窗口函数是这样写的:^1^

sql 复制代码
<函数> over ([PARTITION BY / GROUP BY <字段>] [ORDER BY <字段> [<排序方式>]] [ROWS BETWEEN / RANGE BETWEEN <起始行> AND <终止行>])

从中,我们可以看出,MySQL的窗口函数有三条子句,分别是:PARTITION BY / GROUP BY分组子句ORDER BY排序子句 ,和**ROWS BETWEEN / RANGE BETWEEN限行子句**。

其中,<函数> <字段> <排序方式>这些见名知意。我这里就讲极其重要的三点,先讲第一点,over关键字后面的**PARTITION BYGROUP BY都用于分组** ,但这两者不同的是,PARTITION BY在分组的时候不去重,GROUP BY在分组的时候去重。 ^1^

然后第二点,ROWS BETWEENRANGE BETWEEN关键字都是来限定函数的行的,就跟萝卜白菜,各有所爱 一样,它们也有各自的关注点。ROWS BETWEEN只关注于所给表格数据的行数,RANGE BETWEEN只关注于具体日期范围的连续性。^[1](#ROWS BETWEEN只关注于所给表格数据的行数,RANGE BETWEEN只关注于具体日期范围的连续性。1 "#user-content-fn-1")^ 这样的关注点在BETWEEN后面的<起始行><终止行>那也能体现到。

最后第三点,就是在限行子句ROWS BETWEEN / RANGE BETWEEN后面的<起始行><终止行>那,这要怎么写呢?只需要看下面就行。

  • 如果你在窗口函数的限行子窗口的前面用了ROWS BETWEEN:^1^
    • 起始行:
      • UNBOUNDED PRECEDING(窗口的起始行)
      • N PRECEDING(当前行的前面N行)
    • 当前行:
      • CURRENT ROW(当前行)
    • 终止行:
      • UNBOUNDED FOLLOWING(窗口的终止行)
      • N FOLLOWING(当前行的后面N行)
  • 如果你在窗口函数的限行子窗口的前面用了RANGE BETWEEN:^1^
    • 起始行:
      • UNBOUNDED PRECEDING(窗口的起始行)
      • INTERVAL N <时间单位> PRECEDING(当前行的前面一个相差某个时间的行)
    • 当前行:
      • CURRENT ROW(当前行)
    • 终止行:
      • UNBOUNDED FOLLOWING(窗口的终止行)
      • INTERVAL N <时间单位> FOLLOWING(当前行的后面一个相差某个时间的行)

需要注意的是,如果窗口函数里边写了ORDER BY排序子句,但是没有写ROWS BETWEEN那样的限行子句,那么,在限行子句里边的BETWEEN后面,就默认是UNBOUNDED PRECEDING AND CURRENT ROW了;^1^

而如果,窗口函数里边既没写排序子句,又没写限行子句,那么,在限行子句里边的BETWEEN后面,就默认是UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING了。^1^就以这样的表来举例:

salary_date salary
2020-01-20 5000
2020-02-20 5000
2020-03-20 5000
2020-04-20 5000
2020-05-20 5000
2020-06-20 5000
2020-07-20 5000
2020-08-20 5000
2020-09-20 5000
2020-10-20 5000
2020-11-20 5000
2020-12-20 5000
2021-01-20 10000
2021-02-20 5000
2021-03-20 5000
2021-04-20 5000
2021-05-20 5000
2021-07-20 5000
2021-08-20 5000
2021-09-20 5000
2021-10-20 5000
2021-11-20 5000
2021-12-20 5000

这张表,是小明两年的工资表。现在,如果你想求出小明在这两年中累计发到了多少的钱,就需要用到窗口函数中的限行子句ROWS BETWEENSUM聚合函数来求。

那么,为什么这能用限行子句ROWS BETWEENSUM聚合函数来求呢?因为,ROWS BETWEEN能够限制窗口的行 ,通过UNBOUNDED PRECEDING起始行和CURRENT ROW当前行,使SUM聚合函数能够求出当前行及前面行中的salary中的数据总和。就像这样。

因此,这就是这个查询操作的sql。

sql 复制代码
SELECT 
    salary_date, salary,
    SUM(salary) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) money
FROM
    xiaomings_salary_tb;

执行之后,也的确如此。

现在,你仅仅是解决了第一个问题,还有第二个问题在等着你解答。现在,你觉得光求出了小明累计发到的工资还不够,想要再求一下小明连续发到工资的天数。这要怎么求呢?

首先,如果你没有仔细看的话,就会在不经意间犯错,因为在上表中的2021-05-20这个数据的后面的数据中 ,不是2021-06-20,而直接跳到了2021-07-20,不连续了。

因此,我们就可以借助COUNT聚合函数来为RANGE BETWEEN限行子句中的INTERVAL 1 MONTH PRECEDINGCURRENT ROW求出每一个日期连续的组的头数据,因为如果这里的INTERVAL 1 MONTH PRECEDING跟当前行的上一行数据不符合要求,那么COUNT聚合函数就不会计算。

然后,在用了COUNT聚合函数之后,先用ORDER BYsalary_date进行降序排序,排序好后,就作为子查询来继续查询,之后用WHERE判断出每一个组的头数据,就在COUNT函数返回1的行那。最后LIMIT一下,让查询出的数据只显示前面1行,并让这行只查询salary_date,以此来将@sec_grp_mark设为刚查查询出来的数据,用于在后面分组。

接着,把这个分组的数据用一个变量@sec_grp_mark存起来之后,另写一条sql语句。我们要想求小明连续发到工资的天数,就得要用到ROW_NUMBER这样一个求出表的序列的函数来求出小明连续发到工资的天数,因为如果日期是连续的话,求出的数据也是连续的。

最后,在窗口函数里面,按IF函数来分组,以此来用@sec_grp_mark标记来获取每一个组,做好这一些后,直接执行即可。

sql 复制代码
SELECT 
    salary_date 
INTO 
    @sec_grp_mark
FROM 
(
    SELECT
        (COUNT(*) OVER (ORDER BY salary_date RANGE BETWEEN INTERVAL 1 MONTH PRECEDING AND CURRENT ROW)) groupId, salary_date, salary
    FROM
        xiaomings_salary_tb
    ORDER BY
        salary_date DESC
) tb
WHERE
    groupId = 1
LIMIT
    1;
  
SELECT  
    ROW_NUMBER() OVER (PARTITION BY IF(salary_date < @sec_grp_mark, 1, 2)) comboDays,
    salary_date, salary
FROM
    xiaomings_salary_tb;  

执行之后,如果查询出来的是这样的话,那对了。

好,那么所有关于MySQL窗口函数的写法的重点就都讲完了,接下来就去了解一下关于窗口函数的其它知识吧。

MySQL窗口函数的其它知识点

关于MySQL窗口函数的其他知识点。我这里给他列举了三类,分别是bug类,函数类和执行类。

bug类

bug类呢,别的不提,主要的就有这么一个。在MySQL8.0版本中,如果你用了RANGE BETWEENINTERVAL N <时间单位> PRECEDINGINTERVAL N <时间单位> FOLLOWING的话,就会有这样的一个错误,像这样:

大致的意思呢,就是你在窗口函数的限行子句中使用RANGE BETWEEN的时候,没有用ORDER BY排序子句给日期类型的字段进行排序,会报错。因此,如果你在窗口函数的限行子句中使用RANGE BETWEEN,就需要一个时间类型的字段让窗口函数中的ORDER BY排序子句来进行排序

函数类

在MySQL窗口函数中的<函数>中,可以填入的函数分别有聚合函数,排序类函数和跨行类函数 ^1^,聚合函数不用多说,大家在MySQL中也都遇到过,没遇到过的可以不算,这里面讲的主要就是排序类函数和跨行类函数。

排序类函数

排序类函数,有ROW_NUMBER函数,有RANK函数,还有DENSE_RANK函数。^1^

首先介绍ROW_NUMBER函数,ROW_NUMBER函数,无参,就是用于给一个区间中的每一行从上到下的获取序号 ,这个序号就像这样子:^1^

那么,ROW_NUMBER函数中所说的区间又是什么呢?区间,默认是整张表,被用上窗口函数之后,就是窗口函数分组之后的一个个窗口。 再那么,ROW_NUMBER函数会有什么样的实际用途呢?ROW_NUMBER函数呢,主要的用途就是排名,会用于各种排行榜之中,这里就以这张表为例。

id name device_id gender age university gpa
1 小明 2138 male 21 北京大学 3.4
2 小帅 3214 male NULL 复旦大学 4.0
3 小美 6543 female 20 北京大学 3.2
4 大美 2315 female 23 浙江大学 3.6
5 卞精 5432 male 25 山东大学 3.8
6 薛西 2131 male 28 山东大学 3.3
7 项素典 4321 male 28 复旦大学 3.6

假如,这是一张大学生表,如果你要对该表中的学生进行排名的话,就需要ROW_NUMBER函数,为什么呢?因为这个函数会有排行榜一般的效果,不信就看。

在使用ROW_NUMBER函数来对该表中的学生进行排名时,先降序排序gpa字段,因为gpa是平均学分绩点,gpa的值越大,学生就越厉害,然后,代码就出来了。

sql 复制代码
SELECT 
    id, name, device_id, gender, age, university, gpa,
    ROW_NUMBER() OVER (ORDER BY gpa DESC) 排行
FROM 
    user_profile;

最后执行一下,就有这样结果。

不得不说,看似简单又看似与排名毫不相关的ROW_NUMBER函数,实际上竟会有这般强大的排名功能,真是把他看扁了啊!


然后介绍RANK函数,RANK函数,无参,也会用于各种排行榜,具体的功能跟ROW_NUMBER函数类似。其中,RANK函数还有一个重要的点,那就是,窗口函数中没有ORDER BY排序子句就没用 ,因此,在使用RANK函数的时候,如果你要用到窗口函数,那窗口函数里边就一定要有ORDER BY排序子句。也以这张表为例:

id name device_id gender age university gpa
1 小明 2138 male 21 北京大学 3.4
2 小帅 3214 male NULL 复旦大学 4.0
3 小美 6543 female 20 北京大学 3.2
4 大美 2315 female 23 浙江大学 3.6
5 卞精 5432 male 25 山东大学 3.8
6 薛西 2131 male 28 山东大学 3.3
7 项素典 4321 male 28 复旦大学 3.6

再假如,这是刚才提到的那张大学生表,如果你要对该表中的数据进行排名的话,也可以用RANK函数,而如果,你之后直接RANK函数来搭配上无子句的窗口函数,就像这样的sql一样。

sql 复制代码
SELECT 
    id, name, device_id, gender, age, university, gpa,
    RANK() OVER () 排行
FROM 
    user_profile;

那在你执行它之后,就会发现排行那一字段的数据全部都是1,根本没有排行榜的效果,也就照应了前面说的话:窗口函数中没有ORDER BY排序子句就没用

因此,我们就要在窗口子句里面添加上ORDER BY排序子句,用于给gpa字段排序,所以就在窗口函数里面,给gpa字段添加了ORDER BY排序子句进行降序排序,之后这是改进的sql。

sql 复制代码
SELECT 
    id, name, device_id, gender, age, university, gpa,
    RANK() OVER (ORDER BY gpa DESC) 排行
FROM 
    user_profile;

这时你再执行,就也有排行榜那样的效果了。


最后,就要介绍DENSE_RANK函数了。DENSE_RANK函数,跟RANK函数没有多大的差别,只不过,DENSE_RANK函数可以排出更合理的名次 ,不像RANK函数一样会跨N个名次。以这个查询的sql举例。

sql 复制代码
SELECT 
    id, name, device_id, gender, age, university, gpa,
    RANK() OVER (ORDER BY gpa DESC) 排行
FROM 
    user_profile;

这是刚才用于在刚才的大学生表中排名的sql查询语句,现在,如果把这里面的RANK换成DENSE_RANK的话,执行之后,就这样查询好了。

可以看到,刚才查询出来的结果比上次那个查询的结果的名次更合理了。因此,DENSE_RANK函数能够让排出来的名次变得更合理。


跨行类函数

跨行类函数,有LEAD函数和LAG函数。其中,LEAD函数和LAG函数用途都是关于跨行的,且参数都是一样的,两个默认参数和一个普通参数:要比较的字段N默认值。其中,N后面的参数的都是默认参数,而N的默认参数是1,默认值的默认参数是NULL

但是,不同的是,LEAD函数和LAG函数,它们的返回值却不同。LEAD函数,返回当前行的前面第N行的要比较的字段的数据,如果该行不存在,或者该行超出窗口的范围,就返回默认值

LAG函数,则返回当前行的后面第N行的要比较的字段的数据,如果该行不存在,或者该行超出窗口的范围,就也返回默认值。现在以别的表进行举例。

name point likes
压力哥 2195 6
年级都是一 1340 0
博一也是一年级 1070 0
三秒干碎口算梦 2355 2
研一也是一年级 965 0
... ... ...

假设这是小猿口算APP中的数据表,为了让大家不会看得倒头就睡,这里只显示五行数据,省略的内容先不用管。如果你现在不仅要给该表中的数据按倒序查询出来,并且还查询要出每个用户的下一名的名字及积分,那么就可以用当前学到的换行函数来查询出。

其中,查询表中的数据的事先不用管,主要就是如何用换行函数来查询出每个用户的下一名的名字及积分。很简单,方法如下。

首先,用LAG函数查询出每一行的上一行的name字段的数据;然后,再用LAG函数查询出每一行的上一行的point字段的数据;最后,在每个窗口函数里添上给point字段进行降序排序的ORDER BY排序子句,就行了。

sql 复制代码
SELECT 
    name, point, likes,
    LAG(name) OVER (ORDER BY point DESC) next_name,
    LAG(point) OVER (ORDER BY point DESC) next_point
FROM
    xiaoyuan_calculation_tb;

在你执行之后,这下面便是查询之后的结果。

执行类

在最后,关于窗口函数是如何执行的问题呢,这里我给大家讲一下。

首先,窗口函数会去执行PARTITION BY / GROUP BY分组子句,用于初步给窗口进行切分。如果窗口函数里边只有分组子句,那么就会自动添上限行子句:ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING

然后,窗口函数会去执行ORDER BY排序子句,用于给每一个切分出来的窗口里面的数据进行排序,如果后面没有限行子句,那么在排序子句执行完之后,就会自动加上限行子句:ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

**最后,窗口函数会去执行ROWS BETWEEN / RANGE BETWEEN限行子句,用于指定OVER关键字前面的函数读取到的数据。之后,窗口函数就会调用OVER关键字前面的函数,让这个函数在一个个窗口函数限定的窗口里面进行调用。**等函数执行好后,窗口函数也就执行完了。


至此,关于窗口函数的全部内容,也就在此全部讲完了。

下篇预告

如何在一个数列里边用新手都会玩得666的交换操作生成全部不唯一的序列?(黑马式讲解)

Footnotes

  1. 选自是汽水a的博客MySQL学习八:窗口函数(一) ↩^2^ ↩^3^ ↩^4^ ↩^5^ ↩^6^ ↩^7^ ↩^8^ ↩^9^ ↩^10^ ↩^11^
相关推荐
C66668889 分钟前
SQL Server安装流程
数据库·sqlserver
leegong231111 小时前
Oracle(OCP和OCM)
数据库·oracle
亥时科技1 小时前
旅游全域体验系统(源码+文档+部署+讲解)
java·数据库·开源·旅游·源代码管理
NineData2 小时前
简单几个步骤完成 Oracle 到金仓数据库(KingbaseES)的迁移目标
数据库·mysql·程序员
ADFVBM2 小时前
【Spring Boot】Spring 魔法世界:Bean 作用域与生命周期的奇妙之旅
数据库·spring boot·spring
练小杰3 小时前
【MySQL例题】我在广州学Mysql 系列——有关数据备份与还原的示例
android·数据库·经验分享·sql·学习·mysql
vivo互联网技术3 小时前
Redis 持久化原理分析和使用建议
数据库·c++
多想和从前一样3 小时前
Python(pymysql包)操作MySQL【增删改查】
数据库·mysql
Aishenyanying334 小时前
从零开始设计一个完整的网站:HTML、CSS、PHP、MySQL 和 JavaScript 实战教程
javascript·css·网络·mysql·html·php