datatables 行分组信息展开与折叠的功能实现_SQL高级功能:窗口函数

1.什么是窗口函数

窗口函数可以进行排序,生成序列号等一般的聚合函数无法实现的高级操作。

窗口函数也叫OLAP(Online Analytical Processing)函数,即对数据库数据进行实时分析处理,窗口函数就是为了实现OLAP而添加的标准SQL功能

窗口函数语法:

<

窗口函数可以放以下两种函数:

1.rank , dense_rank , row_number等专用窗口函数

2.能够作为窗口函数的聚合函数(sum , avg , count , max , min等)

由于窗口函数是对where或者group by子句处理后的结果进行操作,所以窗口函数原则上只能写在select字句中

2.如何使用窗口函数

054d98a95fca221b0d58f6c8914b1466.png
班级表

在每个班级内按成绩排名:

select*, rank() over (partition by 班级 order by 成绩 desc) as ranking
from 班级表;

select*, rank() over (order by 成绩 desc) as ranking
from 班级表;

partition by:功能类似于group by,对表进行分组即按班级来进行分组,若省略partition by子句,则将会对所有成绩排序

order by:对分组后的结果进行排序,desc表示降序排序

结果如下

192512dda1fe2272b9a8e7abddaa86df.png
有partition by的结果1

8520441b280f5fecc2802cef52928357.png
省略partition by的结果2

窗口函数group by子句分组和order by字句排序的功能,但是,使用group by分组汇总后改变了表的行数,相当于根据分组的类别将同类的行合并了。同样使用班级表为初始表,具体代码及结果如下:

select 

4d5ad691d91d9d886cef947c6a65fddd.png
使用group by子句后的结果1

ebc750c23fdeab4231a3f6a497f1c492.png
使用窗口函数后的结果2

总的来说,窗口函数在具有分组及排序的功能的同时还不会减少原表的行数。

3.其他专用窗口函数

窗口函数中关于排序三个窗口函数为rank , dense_rank , row_number,这三个专用窗口函数的区别如下:

select

6e378fecaed050b82ce7a67a1544f2a9.png
rank结果

db9defbb21b27304a162f27937df7581.png
dense_rank结果

c7a0513d5ff1064f79cfd8531dfa7b0c.png
row_number结果

rank函数:若排名时有并列名次的行,会占用下一名次的位置,即如结果1所示,会有3个第五名但第五名之后就是第八名了。

dense_rank函数:若有并列名次的行,不占用下一名次的位置,即如结果2所示,即在3个第五名之后是第六名。

row_number函数:不考虑并列名次的情况,排名会对三个相同的成绩根据其他列的情况重新排名。

最后,在上述这三个专用窗口函数中,函数后面的括号不需要任何参数,保持()空着就可以了。

面试经典topN问题:

3a6b568096bed441b49c4e9d55a8f76e.png
score表

按课程号分组取成绩最大值所在行的数据

select

60322d03398b5a6d6a53dfab18bd359e.png
结果

使用关联子查询可以达到目的,注意,子查询中必须添加where a.课程号 = b.课程号 的条件,否则,子查询返回多行的数据,会报错。程序无法运行。

-- 错误示范
select*
from score
where 成绩 = (select max(成绩) from score group by 课程号);

16097452aed348307da148de3b1990d6.png
报错结果

按课程号分组取成绩最小值所在行的数据

select*
from score as a
where 成绩 = (select min(成绩) from score as b where a.课程号 = b.课程号 group by 课程号);

5d890653eb0765fcdd738636ea082c9d.png
结果

同样适用关联子查询可以达到目的。

查询每名学生成绩前两名的纪录

方法一:先使用group by 将课程分组,之后再通过order by对成绩排序,并用limit限制输出的行数,将每个学生最高的两门课的成绩取出来

(select*
from score
where 学号 = '0001'
order by 成绩 desc
limit 2)
union
(select*
from score
where 学号 = '0002'
order by 成绩 desc
limit 2)
union
(select*
from score
where 学号 = '0003'
order by 成绩 desc
limit 2);

529f9aec25a175781b9692f76f6609f7.png
结果

方法二:使用窗口函数

select*,row_number() over (partition by 学号 order by 成绩 desc) as ranking
from score;

eb1d9c780e48167acddd964d64d4f706.png
结果

为得到每个学生成绩最高的两门课的成绩,需要加ranking<=2的条件

select *,row_number() over (partition by 学号 order by 成绩 desc) as ranking 
from score
where ranking <=2;

1f6f984840d8e15eb87dcb2bb7354d6d.png
报错

直接在where之后加ranking<=2的条件是错误的,报错信息如上。这是因为select子句的运行顺序是在where子句之后的,在where子句运行时ranking这一列还没被定义。

正确代码如下:

select *
from (select*,row_number() over (partition by 学号 order by 成绩 desc) as ranking
from score) as a
where ranking <=2;

这里在from子句里使用了子查询,相当于通过窗口函数建立了一个新表

eb1d9c780e48167acddd964d64d4f706.png
新表

然后在新表中查找ranking<=2的行,得到的结果如下:

e8b526d0c0d7785ae98213422bc283ed.png
结果

将此题的代码改一下,可以作为此类问题的常用解法,格式如下:

select*
from (select*,row_number() over 
(partition by <要分组的列名> order by <要排序的列名> desc)
from <表名>) as a
where ranking [<=N];

代码块中[]内的部分可以根据问题修改,查询每组最大/最小的N条记录

关于MySQL 8.0版本的其他窗口函数,具体介绍见:

12.21.1 Window Function Descriptions​dev.mysql.com

4.聚合函数作为窗口函数

聚合窗口函数和上面提到的专用窗口函数用法完全相同,只需把聚合函数写在窗口函数的位置即可,但是函数后面括号不能为空,需要指定聚合的列名

以班级表为例:

860c05d294949a8d72ba236a40f06c11.png
班级表
-- 聚合函数的窗口函数

7e5a3bf0b644129461068a291452bbb7.png
无partition by结果

以sum函数举例,sum的窗口函数相当于是在对自身这一行的记录及位于自己记录之上的记录进行求和,比如:对于学号0005的这一行来说,current_sum就是对0001,0002,0003,0004,0005这五个学生的成绩求和,可得这五名学生的总成绩。

avg,max,min,count这些聚合函数的窗口函数都是同样的原理,avg(成绩)就是查询包括自身这一行在内以上所有行的平均值,max(成绩)就是查询包括自身一行在内以上所有行的最大值,min(成绩)就是查询包括自身一行在内以上所有行的最小值,count(成绩)就是查询包括自身一行在内以上所有行的行数

当我在窗口函数中加入基于班级的分组时,可得在对班级分组后在各班级内分别求和,求平均,查询最大,最小值以及计数。,结果如下:

select*,
sum(成绩) over (partition by 班级 order by 学号) as current_sum,
avg(成绩) over (partition by 班级 order by 学号) as current_avg,
max(成绩) over (partition by 班级 order by 学号) as current_max,
min(成绩) over (partition by 班级 order by 学号) as current_min,
count(成绩) over (partition by 班级 order by 学号) as current_count
from 班级表;

50ba4c483a75fa13dcb7f905d9a58550.png
有partition by&amp;amp;order by结果

当没有order by时,代码及结果如下:

select*,
sum(成绩) over (partition by 班级) as current_sum,
avg(成绩) over (partition by 班级) as current_avg,
max(成绩) over (partition by 班级) as current_max,
min(成绩) over (partition by 班级) as current_min,
count(成绩) over (partition by 班级) as current_count
from 班级表;

86a8687c9ec98f18a3459ca13d049780.png
无order by结果

当无order by之后,窗口函数会在分组之后直接对每组的所有数据计算,又由于窗口函数并不会缩减行数,故每行的窗口函数结果相同。

聚合函数作为窗口函数,可以在每一行的数据里直观的看到,截止到本行数据,统计数据是多少(最大值,最小值等)。同时可以看到每一行数据,对整体统计数据的影响。

练习:

a198a0fd1e48f0469a3e44976dc2257f.png
初始表

问题:查找单科成绩高于该科平均成绩的学生名单

此题的解题方法与求topN的问题的解法类似,都需要先重新查询建立一个新表,然后在新表中查询。具体代码,及结果如下:

-- 查询各科成绩高于该科目平均成绩的学生名单

d2dc54e82529e9fbc6bf7cfe674d2993.png
新表

c577236341348d3bece4db188c8b270a.png
查询结果

还有一种方法就是使用关联子查询,同样可以查询各科高于平均成绩的学生,代码及结果如下:

select*
from 各科成绩表 as a
where 成绩 >(select avg(成绩)
from 各科成绩表 as b
where a.科目 = b.科目
group by 科目);

c3be7e6854e2afa6c8d00a73fca771b0.png
结果

5.窗口函数的移动平均

同样以班级表举例:

b32ab06ff2fd71c15bb579c963a082b0.png
班级表
select*,avg(成绩) over (order by 学号) as current_avg
from 班级表;

a0e77072a97bf4051ab7251367d79591.png
结果1
select*,avg(成绩) over (order by 学号 rows 2 preceding) as current_avg
from 班级表;

8ff538582b01d5a918445556bd66ad26.png
结果2

经过对比可以发现,两次计算的平均值不一样,这是因为在第二个结果的代码里加入计算条件rows...preceding...,每一行得到的结果是自身这一行的记录及前两行的平均,如:学号0006这一行的current_avg表示的是0004,0005,0006这三行的成绩的平均值,即83,86,92的平均值。

每一行的得到的结果,都是当前行和前面两行的平均,想要计算当前行与前n行(共n+1行)的平均时,只需调整rows...preceding中间的数字即可。

不仅如此,还可以计算当前数据与前n行,后n行数据的移动平均,比如,我想要计算每行数据与上一行,下一行数据的平均,只要将preceding关键词和following关键词结合就可以了,举例如下:

select*,
avg(成绩) over (order by 学号 rows between 1 preceding and 1 following) 
as current_avg
from 班级表;

5041ecb59384c9562af4276892b596a0.png
结果

6.总结

窗口函数可以解决很多普通group by&order by子句难以解决的业务问题。

  1. 可以在不改变行数的情况进行排序,分组,求和,求平均等操作;
  2. 可以对特定的某几行求和,求平均等等;
  3. 窗口函数原则上只能写在select子句中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值