实用sql技巧,开窗函数

实用sql技巧,开窗函数

最近在牛客解sql题目时,经常有一类题目要求对表中的某一列进行排序

在查看提供的题解时经常看到使用开窗函数的解决方案

虽然不用开窗函数也能通过子查询的方式获取正确答案,但是

比较复杂难以理解,

所以很有必要学习

原题放送

在牛客刷题有一个通过题目个数的(passing_number)表,id是主键,简化如下:
请添加图片描述

第1行表示id为1的用户通过了4个题目;

第6行表示id为6的用户通过了4个题目;

请你根据上表,输出通过的题目的排名,通过题目个数相同的,排名相同,此时按照id升序排列,数据如下:
请添加图片描述

思考:

​ 题目只有一张表,两个字段,看似很简单,

​ 如果没有t_rank要组合的话只需要

select*from passing_number order by number desc;

​ 但我们现在需要组合出t_rank,

在不使用开窗函数情况下的解题思路:

我开始时想通过聚合函数构造一个字段,但事情并不简单,需要得出排名并非能够通过一个聚合解决

需要使用通过子查询来得出排名的值

具体实现(参考该题的第一名的解答):

要得到通过题目的排名,比如你通过了8分,,你同学a也通过了8分,找到大于等于你的成绩,一个9分,一个8分,一个8分,去重复,就一个9,一个8,count一下总数,第2名,如果有三个同学通过了7个呢,同理,9,8,8,7,7,7 后面比这个少的,已经死在了筛选条件,去重,9,8,7,count=3,

确实,如果我们得了10分,没有人超过我们,那么找到大于等于我的成绩,及10,count一下就是1了,即第一名,

故总结为

select COUNT(DISTINCT number) from passing_number where passing_number.number>=X
#X为我们的得分数

如此,加上最初的那句简单sql,即可以得出本题的一种答案

select a.id,a.number,
(select COUNT(DISTINCT b.number) from passing_number as b where b.number >= a.number) from passing_number as a order by a.number desc;
#将外查询的number的值给内查询作为参数即可通过内查询得出该分数对应的排名

请添加图片描述

显然这个逻辑还是有一点复杂的,

下面先显示使用开窗函数的答案

SELECT
    *,
    dense_rank() over ( ORDER BY number DESC ) `rank` 
FROM
    passing_number 
ORDER BY
    `rank`,
    id;

这个答案也可以达到一样的效果

请添加图片描述

这个sql语句则简洁很多,现在需要理解的就只是这个开窗函数了

开窗函数

前言:老版本的mysql并不支持开窗函数,如需使用请使用mysql8以上的版本

开窗函数的定义:

它可以理解为记录集合,开窗函数也就是在满足某种条件的记录集合上执行的特殊函数。对于每条记录都要在此窗口内执行函数,有的函数随着记录不同,窗口大小都是固定的,这种属于静态窗口;有的函数则相反,不同的记录对应着不同的窗口,这种动态变化的窗口叫滑动窗口。开窗函数的本质还是聚合运算,只不过它更具灵活性,它对数据的每一行,都使用与该行相关的行进行计算并返回计算结果。

开窗函数与聚合函数:

聚合函数是将多条记录聚合为一条;而开窗函数是每条记录都会执行,有几条记录执行完还是几条。
聚合函数也可以用于开窗函数中。

开窗函数的使用

​ 从开窗函数的定义中,可以了解到开窗函数比普通的聚合函数更加的灵活,功能也更加强大。但并不是很好理解,那么先从它的使用开始吧


开窗函数名([<字段名>]) over([partition by <分组字段>] [order by <排序字段> [desc]] [<窗口分区>])

开窗函数的一个概念是当前行,当前行属于某个窗口,窗口由
over关键字用来指定函数执行的窗口范围,如果后面括号中什么都不写,则意味着窗口包含满足where条件的所有行,开窗函数基于所有行进行计算;如果不为空,则有三个参数来设置窗口:

partition by子句:

按照指定字段进行分区,两个分区由边界分隔,开窗函数在不同的分区内分别执行,在跨越分区边界时重新初始化。

order by子句:

按照指定字段进行排序,开窗函数将按照排序后的记录顺序进行编号。可以和partition by子句配合使用,也可以单独使用。

frame子句:

当前分区的一个子集,用来定义子集的规则,通常用来作为滑动窗口使用。

常用的开窗函数:

请添加图片描述

这里直接在官网查了一下,网址如下MySQL :: MySQL 8.0 Reference Manual :: 12.21.1 Window Function Descriptions

使用案例

以上大部分函数的使用效果用例都可以在上面的官网中看到示例,

下面是在实际中的使用:

如上面的原题,

​ 分析其开窗函数的含义:

SELECT
    *,
    dense_rank() over ( ORDER BY number DESC ) `rank` 
FROM
    passing_number 
ORDER BY
    `rank`,
    id;
    #分析:使用的是dense_rank()即不跳排名的方式计算所有行(因为其并没有写partition by...)的排名通过number的倒序

突然发现了新东西貌似比较有趣,明天接着更

具体使用:

构造一张表,在其中插入一些信息

create table `studentgrade`(
id int(11) PRIMARY KEY auto_increment,
 sname VARCHAR(20),
 sex VARCHAR(5),
 theclass VARCHAR(20),
 grade_ch_zn DOUBLE(5,2),
 grade_en DOUBLE(5,2),
 grade_math DOUBLE(5,2) 
)engine = InnoDB charset = utf8 auto_increment = 1;
insert into studentgrade(sname,sex,theclass,grade_ch_zn,grade_en,grade_math) values('billy','man','classOne','111.11','78.43','103.50');
insert into studentgrade(sname,sex,theclass,grade_ch_zn,grade_en,grade_math) values('Tony','man','classTwo','95.76','105.23','77.64');
insert into studentgrade(sname,sex,theclass,grade_ch_zn,grade_en,grade_math) values('Lily','woman','classOne','124.75','80.56','101.45');
insert into studentgrade(sname,sex,theclass,grade_ch_zn,grade_en,grade_math) values('Mark','man','classOne','105.39','90.21','109.67');
insert into studentgrade(sname,sex,theclass,grade_ch_zn,grade_en,grade_math) values('Lisa','woman','classTwo','99.50','112.45','101.45');

在这里插入图片描述

排名函数

我们先利用几个开窗函数中的排名函数来测试一下:

select sname,dense_rank() over (order by grade_math desc) as `数学成绩排名(名次连续)`,
			 rank() over(order by grade_math desc) as `数学成绩排名(名次不连续)`,
			 percent_rank() over (order by grade_math) as `排名百分比`,
             row_number() over (order by id) as `记录所在的行号`
             from studentgrade order by `数学成绩排名(名次连续)`;
             #我的over里没写partition by部分则默认是表中所有的行,
             #使用order by 为dense_rank()函数指定排序条件即按数学成绩的高分顺序计算排序
             
             #percent_rank()函数的计算规则为(rank - 1) / (rows - 1),rows为分区的行数.
             #在over字句里没有指定分区,则分区为整张表,即rows=表的行数,即rows = 5
             #此次的rank即按percent_rank()的over字句条件算出的rank() 
             #即以Mark学生为例,他的rank 按 grade_math的正序排正好为5
             #所以Mark学生的percent_rank()的值即为(5-1)/(5-1) = 1;
             #其他学生即按上类推
             #突然发现此处的percent_rank()可以解释为数学成绩超越总记录的百分之多少

结果:
在这里插入图片描述

累计分布和ntile函数

再测试一下计算累计分布值,和Bucket number的开窗函数

我先查一下bucket number,发现网上并没有这个东西的准确信息,只能在官网里看其定义了

请添加图片描述

以上表的数据测试一下:

select sname,cume_dist() over w,ntile(2) over w	
 from studentgrade WINDOW w as (order by grade_math);
 #计算累积分布值,与查询被分成两个桶的记录们的桶号
 #Window关键子的用法是给当前窗口起别名

Window关键字的用法可以参考

MySQL8中的关键字window与窗口函数_程序员先森的博客-CSDN博客

结果为
在这里插入图片描述

LAG与LEAD函数(前后)

在这里插入图片描述
在这里插入图片描述

测试:

select sname,grade_math,lag(grade_math) over ( order by grade_math) as `滞后一位的成绩`,
					  lead(grade_math) over ( order by grade_math) as `向前一位的成绩`
                         from studentgrade order by id;
                         #将grade_math传入lag和lead作为参数,则可以计算出分区(没填默认为整张表)中
                         #在参数值前后的值

结果:
在这里插入图片描述

分区的使用

通过partition by 关键字确定开窗函数的分区,

例:

select *,avg(grade_ch_zn) over (partition by theclass order by grade_ch_zn) as `前一个学生和后一个学生的语文平均分`
		from studentgrade;
#指定theclass属性作为分区的条件,即一个相同的theclass值为一组进行计算,当分区值(即theclass值)变化后#则重新初始化计算
#yan (105.39 + 111.11)/2 = 108.25 

结果:
在这里插入图片描述

Frame字句的格式

官网的介绍:MySQL :: MySQL 8.0 Reference Manual :: 12.21.3 Window Function Frame Specification

在这里插入图片描述

与窗口函数一起使用的窗口的定义可以包括Frame子句。 Frame是当前分区的子集,Frame子句指定如何定义子集。

Frame是相对于当前行确定的,这使得Frame能够根据当前行在其分区内的位置在分区内移动。

例子: 通过将一个框架定义为从分区开始到当前行的所有行,您可以计算每行的运行总数。

​ 通过将框架定义为在当前行的任一侧扩展 N 行,您可以计算滚动平均值。

官网的介绍很复杂,这里有一篇更优秀的文章可以参考

https://blog.csdn.net/kejiayuan0806/article/details/103297893

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗马苏丹默罕默德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值