学习数据库的过程中,练习题目的时候我们经常会遇到如下等问题:
查询连续三个月都投稿的作者姓名?——期刊表练习题
查询同年连续三个月都有违法记录的当事人信息?——交通执法练习题
用户登录表中连续登录了三次及以上的用户?
查询产品号存在断号的情况、及断开的号码?
可以看到,以上这些问题似乎都与“连续”相关,又各有不同,例如,第一问仅求解连续三个月有投稿记录,那么如下表格中,当我们把日期升序排序后,第1到3行是满足的,4到5行不满足:
时间 序号 2023-01-03 1 2023-02-11 2 2023-03-22 3 2023-05-24 1 2023-06-10 2
第二问在此基础上多加了一个“同年”,那么如下表格中只有第3行到5行是满足要求的,类似地,题目还可以换为“同一天连续n个小时”、“同一月连续n天”,甚至两个、三个条件联立,如“同一年同一月同一小时连续n秒”,这里仅以同年连续3个月举例,观察下表的序号与第一个表格有什么区别:
时间 序号 2022-01-03 1 2022-02-04 2 2023-03-08 1 2023-04-02 2 2023-05-01 3 2023-07-09 1
这就对我们产生了启发,我如果能使用教材上简单介绍的row_number
(或row_num
),对时间序列进行排序、并新加一列表示排序的话,似乎我们把这个加了排序的新表用一个where条件进行筛选后(比方说,where 序号为1,2,3?)我们就能直观地得到我们想要的结果。那这个思路是否可行呢?
开窗函数 关于开窗函数,百度或者谷歌查询后有很多介绍,在这里限于篇幅不作详细介绍,我以自己的理解作直白的解释:
函数形式 首先,开窗函数用在sql语句的select 与 from 之间,在我们应试时,能用到的就是:排序+聚合类函数(sum、max、min、avg、count)的升级版+偏移,用于查询出一列,且是新的一列(原表没有) ,其基本的语法如下(以row_number()为例):
1 2 3 select row_number () over (partition by xx order by xx)from table (表名)
我以表格的形式进行说明:
关键字 作用 是否可省略 over 告诉数据库,要开窗查询 × partition by 按…分组 √ order by 按…排序 聚合类√,排序类×,偏移类×
这个over
就是所谓的“开窗函数”的关键字,要开窗必须跟上这个关键字,partition by
为“按…分组”的意思,可省略,具体看你的业务逻辑,因为不分组一样能求和、排序,order by
为“按…排序”的意思。只需要记住这有哪些开窗函数(下面会作介绍),及这三个关键字我们就能使用开窗了。
1.在原表的基础上新开一列
2.新列中,填充的值情况如下:
(1)序号,标明这一行某个数据的排序情况。
(2)求和、最大、最小、平均值、计数,这一行中,某个值隶属于的“组”中,它们这一组的求和、最大、最小、平均值、计数(结合partition by),其中,sum求和能与order by连用,进行累加
(3)偏移,新开的一列为原来某一列数据向前偏移一行或多行,或者向后偏移一行或多行
3.新开的列,全部是“伪列” ,需要再套一层父查询,把查询结果“固定”下来。
以上这些说明,看起来会比较抽象,下面举例的时候可以回来这里对照查看和理解
开窗函数种类 Sql server或oracle中开窗函数种类很多,在此仅介绍常用的几个:
以下5个,最常与partition by 连用:
sum() over()——开窗求和
max() over()——开窗求最大
min() over()——开窗求最小
avg() over()——开窗求平均
count() over()——开窗计数(注:不能使用distinct)
以下3个,为排序函数,最常与partition by + order by 连用:
row_number() over()——开窗排序,值相同时序号不等
rank() over()——开窗排序,值相同时序号相等,且下一个值序号跳开
dense_rank() over()——开窗排序,值相同时序号相等,且下一个值序号连续
以下2个,为偏移函数,常与order by 连用,加上partition by可实现组内偏移:
lag() over()——开窗向后偏移,默认值为lag(某一列,1),即步长为1,向后偏移一行,即本行显示某一列的上一行值
lead() over()——开窗向前偏移,默认值为lead(某一列,1),即步长为1,向前偏移一行,即本行显示某一列的下一行值
例:自行创建表和插入以下数据,进行观察,demo表中第一列为id,第二列为数字num:
1 2 3 4 CREATE TABLE demo( id INT4, num INT4 );
1 2 3 INSERT INTO demo VALUES (1 ,3 ),(2 ,8 ),(2 ,3 ),(1 ,4 ),(2 ,5 ),(2 ,7 ),(2 ,2 ),(1 ,3 ), (1 ,5 ),(1 ,5 ),(2 ,20 ),(1 ,7 ),(3 ,7 ),(2 ,1 ),(3 ,7 ),(3 ,7 ),(3 ,9 );
以sum() over()为例 聚合类,求和,partition by以及order by都可以跟,只跟partition by时居多,表示分组求和的意思。
以下语句、及结果:
可以完全省略partition by和order by,此时“降维”成普通sum函数,区别仅在于普通聚合函数最后只显示一行求和值。而开窗在每一行后面都添加一列,该列所有值都一样,都是对某列进行求和
1 2 3 4 SELECT * , SUM (num) OVER () sum_numFROM DEMO;
以下语句、及结果:
加了partition by以后,可以看到,结果发生变化,按组求和,并把同一组放在一起给你显示,可以看到,每一行对应的这个开窗值,都是与该组的“属性”相关的,这个属性指的就是:它们之所以都一样,是因为它们同属于一个id,即同一个partition by ,因此当使用下面第二个代码块的语句(使用distinct,使用内嵌视图)就会使结果和普通聚合一模一样。
1 2 3 SELECT * , SUM (num) OVER (PARTITION BY ID) sum_numFROM DEMO;
1 2 3 4 5 6 7 SELECT DISTINCT ID, sum_numFROM ( SELECT * , SUM (num) OVER (PARTITION BY ID) sum_num FROM DEMO );
请自行观察下列语句的执行结果(acc_num取名 accumulate的意思):
1 2 3 SELECT * , SUM (num) OVER (ORDER BY NUM) acc_numFROM DEMO;
剩余几个开窗函数(聚合+排序) 从该max开始,下面几个聚合类+排序我这里统一贴上一个语句,你可以单拿出一个,去试试看同时加分组排序,只要分组、排序中的一个、什么都不要,观察语句结果:
1 2 3 4 5 6 7 8 9 SELECT * , MAX (num) OVER (PARTITION BY ID) max_num, MIN (num) OVER (PARTITION BY ID) min_num, AVG (NUM) OVER (PARTITION BY ID) avg_num, COUNT (* ) OVER (PARTITION BY ID) cnt_num, ROW_NUMBER () OVER (PARTITION BY ID ORDER BY NUM) rn_num, DENSE_RANK () OVER (PARTITION BY ID ORDER BY NUM) drk_num, RANK () OVER (PARTITION BY ID ORDER BY NUM) rk_numFROM DEMO;
偏移 lag() over()及lead() over()单独做一个,因为刚才的语句分了组,虽然偏移也支持组内偏移,但可能看起来会不太清晰,观察以下语句的结果:
1 2 3 4 5 6 SELECT * , lag (num) OVER (ORDER BY NUM) lag_num, lag (num,2 ) OVER (ORDER BY NUM) lag2_num, lead (num) OVER (ORDER BY NUM) lead_num, lead (num,2 ) OVER (ORDER BY NUM) lead2_numFROM DEMO;
1 2 3 4 5 6 SELECT * , lag (num) OVER (PARTITION BY ID ORDER BY NUM) ctlag_num, lag (num,2 ) OVER (PARTITION BY ID ORDER BY NUM) ctlag2_num, lead (num) OVER (PARTITION BY ID ORDER BY NUM) ctlead_num, lead (num,2 ) OVER (PARTITION BY ID ORDER BY NUM) ctlead2_numFROM DEMO;
可以看到,lag(num)或lead(num)只跟order by的时候,新开的这一列是把Num列往后或往前偏移了一行,因此上面第一个语句查询结果中第3列(lag)第1行、第5列(lead)最后一行出现了null值。lag(num,2)带第二个参数的时候,表示步长,就让num列往后偏移了2行,因此第4列前2行都是null值。
第二个语句中,lag(num)或lead(num)与partition by 和 order by 同时连用时,我们发现组内也可以进行偏移,于是出现了上图中按ID进行分组后的偏移
更大胆一点,我们可以让lag(num,x)中的步长x嵌套一个子查询:
1 2 3 SELECT * , lead (num,(SELECT MAX (ID) FROM DEMO)) OVER (ORDER BY NUM) lead_numxxFROM DEMO;
以题目学 ok了解了开窗的基本概念之后,用题目来看下实际运用:
1.投稿表题目 建TGB表语句:(如有请忽略)
1 2 3 4 5 6 7 8 9 CREATE TABLE TGB ( ZZH character (4 ) NOT NULL , QKH character (4 ) NOT NULL , TGRQ timestamp NOT NULL , WZMC varchar (50 ) , SGJG varchar (6 ) , CONSTRAINT PK_TGB PRIMARY KEY (ZZH, QKH, TGRQ) ) ;
插入数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 INSERT INTO TGB ( ZZH,QKH,TGRQ,WZMC,SGJG) VALUES ('zz01' ,'qk02' ,'2007-06-03 00:00:00' ,'信息安全的第二道防线——入侵检测系统' ,'通过' ), ('zz01' ,'qk02' ,'2018-11-02 11:24:00' ,'地铁界限系统的研究' ,'未通过' ), ('zz01' ,'qk03' ,'2007-07-08 00:00:00' ,'浅论教育和谐下的师德建设' ,'未通过' ), ('zz01' ,'qk04' ,'2006-03-04 00:00:00' ,'对全球化的一种全体化思考' ,'通过' ), ('zz02' ,'qk01' ,'2006-03-01 00:00:00' ,'基于VC与Ansys的参数化有限元分析' ,'未通过' ), ('zz02' ,'qk02' ,'2008-06-09 00:00:00' ,'一种基于移动agent技术的分布式数据检索系统的实现' ,'通过' ), ('zz03' ,'qk02' ,'2007-08-29 00:00:00' ,'无线传感器网络的研究' ,'通过' ), ('zz03' ,'qk02' ,'2008-02-01 00:00:00' ,'基于Web的考试系统新方案' ,'通过' ), ('zz03' ,'qk03' ,'2007-06-05 00:00:00' ,'论高效青年教师师德建设' ,'通过' ), ('zz04' ,'qk02' ,'2008-03-09 00:00:00' ,'基于J2EE的分布式事务研究' ,'通过' ), ('zz04' ,'qk05' ,'2008-02-05 00:00:00' ,'刍议医话' ,'未通过' ), ('zz06' ,'qk01' ,'2007-07-08 00:00:00' ,'CORBA和Java的结合使用' ,'未通过' ), ('zz06' ,'qk01' ,'2008-01-03 00:00:00' ,'高速公路通信系统的整合利用初探' ,'未通过' ), ('zz06' ,'qk02' ,'2006-01-26 00:00:00' ,'个性化协同学习环境本体的研究' ,'通过' );
问:找出投稿表中同一年连续两个月都有投稿记录的作者信息?
最终要的是“作者信息”,因为原题考了连接查询,即还存在另一个ZZB(作者表),这里就不给了,因为本表中已有了zzh(作者号)的信息,我们能在本表里筛选出来满足条件的结果,那么用一个join就能把另一个表的信息连进来,因此连接查询不是关键,而在于解决连续的问题。
那我们不管三七二十一,先把窗开一下看看,partition by
采用zzh、和year(tgrq),即要根据同一作者、同一年份去分组,order by采用tgrq升序排列:
Step 1——
1 2 3 SELECT * , ROW_NUMBER () OVER (PARTITION BY zzh,YEAR (TGRQ) ORDER BY TGRQ) rnFROM TGB
可以看到,rn一列,已经按我们指定的同一人、同一年、日期升序进行排列,观察一下,可看到zz01在2007年在6月,7月是投过稿的,因此序号标出了1和2、zz03在2007年6月和8月投过稿,也标出了1和2、zz04在2008年2月和3月投过稿,标出了1和2。其他地方如果年份上出现了断开,因此不会出现2。
现在似乎我们已经知道结果了,是zz01、zz03、zz04么?
zz03不是,他的6月和8月没连上,但rn依然给到了他1和2。为什么?因为rn只根据日期升序进行了排序,其他全为1的那些“行”,是因为他们根本没有“同年”多次投稿的记录,所以rn只给到了1
Step 2——
核心思想 的一步,把投稿日期(TGRQ)在月份 上减去 rn,这一点是运用开窗函数解决连续的核心思想,关键所在
这里代码块我做了缩进,以美观些,内嵌视图的这个“子查询”其实就是上一步的结果,只不过是把Step 1的结果表作为了from后面的一坨,相当于在上一步基础上又新开出了一列,这一列命名为delta。观察结果:
1 2 3 4 5 6 7 SELECT * , dateadd('month' ,tgrq,- rn) deltaFROM ( SELECT * , ROW_NUMBER () OVER (PARTITION BY zzh,YEAR (TGRQ) ORDER BY TGRQ) rn FROM TGB )
再来关注delta列的情况:
zz01的6月和7月分别减1,2后,都变成了5月
zz03的6月和8月分别减1,2后,变成了5月和6月
zz04的2月和3月分别减1,2后,都变成了1月
因为row_number()优秀的“天然连续”,所以作差这一步的思想,是抹平 某一个序列与自然数列的“差距”,例如:
1 2 3 4 5 (1 , 2 , 3 , 5 , 7 , 8 , 9 ) (对应位置作差) (1 , 2 , 3 , 4 , 5 , 6 , 7 )
连续的部分有:
(1,2,3) => (0,0,0)
(7,8,9) => (2,2,2)
非连续的部分:
(3,5) => (0,1)
(5,7) => (1,2)
所以,日期减去rn后,如果delta相同,就表明连续,且有几个相同的,就连续几次
Step 3——
把刚刚Step 2的结果作为内嵌视图,使用一次普通聚合,把zzh作者号选出来,数一下delta有几个,这里还可以显示出开始投稿日期,因为数的是delta为2的有几个,因此trunc掉delta的日、时、分、秒部分,dateadd月份加1,再data_format转为’YYYY-MM’形式
1 2 3 4 5 6 7 8 9 10 11 12 13 SELECT zzh 作者号, date_format(dateadd('mm' ,trunc(delta,'month' ),1 ),'%Y-%m' ) 开始投稿日期, COUNT (* ) 连续投稿次数FROM ( SELECT * , dateadd('month' ,tgrq,- rn) delta FROM ( SELECT * , ROW_NUMBER () OVER (PARTITION BY zzh,YEAR (TGRQ) ORDER BY TGRQ) rn FROM TGB ) )GROUP BY ZZH,trunc(delta,'month' )HAVING COUNT (* )= 2 ;
为什么最后一步不开窗?数delta也可以用count,但总体会套4层代码,且最外层只是用来做where筛选,不太好看而已,代码如下:
这样也能做,只是最大程度保住了原表的样子(group by因聚合会丢弃很多列信息),但同时也能看到该作者连续投稿月份最早投的是哪个文章,通过与否(意义不是很大)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SELECT * FROM ( SELECT * , COUNT (* ) OVER (PARTITION BY ZZH,date_format(delta,'%Y%m' )) cnt FROM ( SELECT * , dateadd('month' ,tgrq,- rn) delta FROM ( SELECT * , ROW_NUMBER () OVER (PARTITION BY zzh,YEAR (TGRQ) ORDER BY TGRQ) rn FROM TGB ) ) )WHERE cnt= 2 AND rn= 1 ;
2.订单表题目 建DDB(订单表)语句:
1 2 3 4 5 6 7 CREATE TABLE DDB( ddh char (4 ) NOT NULL , zyh char (3 ) NOT NULL , khh char (4 ) NOT NULL , qdrq date NOT NULL , je decimal (10 ,2 ) );
导入数据:
这里因为做题的时候给的是excel,且数据较多,这里如没有数据,请自行下载该excel并导入你的数据库:
问:订单表中的订单号应该是连续编号,找出订单表中的订单号是否有重号?重号是什么?
重号问题,使用开窗函数来做的话,最先想到的是用dense_rank(),因为这个排序函数会让两个相等的值获得一样的排名,观察这个语句在发生重号处的结果:
1 2 3 SELECT ddh, DENSE_RANK () OVER (ORDER BY ddh::INT ) rnFROM DDB
可以看到,只需要把第二列排序结果里出现两次及以上的筛选出来,就得到了发生重号的地方
直接贴上完整的查重语句:
1 2 3 4 5 6 7 8 9 SELECT DDH 订单号, COUNT (* ) 重复次数FROM ( SELECT ddh, DENSE_RANK () OVER (ORDER BY ddh::INT ) rn FROM DDB )GROUP BY DDHHAVING COUNT (* )>= 2 ;
可以想见,本题里最多只出现了2次重号,如果使用自连接,确实只需要join
一次即可,但如果重号发生很多,使用开窗的这种方式不管重了多少次都可以检测出来。本题也可以用row_number(),但是语句会比dense_rank()复杂,语句贴在下面
这里最外层用到了lead偏移,思考一下,只偏移一行,能否找出所有重号的行?如果这题里重号的不止2个,而是3个,4个。。,答案是可以,因为作差后,只要重复,那么重号的那部分的下一行,差值都比他小,所以where条件后面一定可以筛出来。最后的结果每个重号有一行,可以推知,该号只重了一次,如果重两次的话,筛选结果里,该号会有两行
1 2 3 4 5 6 7 8 9 10 11 12 13 SELECT * FROM ( SELECT * , lead (new_ddh) OVER (ORDER BY ddh::INT ) lag_new_ddh FROM ( SELECT * , DDH::INT - rn new_ddh FROM ( SELECT ddh, ROW_NUMBER () OVER (ORDER BY ddh::INT ) rn FROM DDB ) ) )WHERE new_ddh> lag_new_ddh;
问:订单表中的订单号应该是连续编号,看看订单表中订单号是否存在断号,断号是什么?
先来思考一下,现在我们已经可以用row_number()生成一个自然序列了,如果用这个自然序列左连接 订单号,那么存在断号的地方,其肯定会有NULL值,然后再把NULL值筛选出来,这个思路可行么?
语句如下:
1 2 3 4 5 6 7 8 9 10 11 SELECT rn,new_ddhFROM ( SELECT * FROM ( SELECT DDH, ROW_NUMBER () OVER (ORDER BY ddh::INT ) rn FROM DDB) a LEFT JOIN (SELECT DDH new_ddh FROM DDB) b ON a.rn= new_ddh )WHERE new_ddh IS NULL ;
前面的部分看起来一切OK,但——订单号本来就存在重号、断号的情况,使用row_number()的话,行号必然会小于最大订单号,也就是说会筛漏掉一部分。
开窗函数中的row_number()是根据原表中有多少行来生成的,所以这里用这种方法的话,我们得自己考虑生成一个自然数列,而且这个数列的最大值只需要等于最大订单号即可,所以这里使用了level和connect by语法:
1 2 3 4 5 6 7 8 9 10 SELECT * FROM ( SELECT LEVEL num FROM dual CONNECT BY LEVEL<= (SELECT max (ddh::INT ) FROM DDB)) aLEFT JOIN (SELECT DDH::INT FROM DDB) bON a.num= b.ddhWHERE ddh IS NULL ORDER BY num;
对于连续来说,更常用的还是row_number()结合作差,这个题应该怎么解?
不好的地方:筛选结果为断号的下一个号,只知道断了几个号,但好在思路更通用一些
这个表的意思:44之前发生了断号,且断了5个号——>应该为39.40,41,42,43
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 SELECT * , delta- lag_delta cnt_emptyFROM ( SELECT * , lag (delta) OVER (ORDER BY ddh::INT ) lag_delta FROM ( SELECT * , DDH- rn delta FROM ( SELECT DDH, ROW_NUMBER () OVER (ORDER BY DDH::INT ) rn FROM DDB ) ) )WHERE delta> lag_delta OR lag_delta IS NULL ;
3.来个习题吧 tb_sale_amount为商品数量表,
good_category——商品类型
sale_date——销售日期
amount——销售数量
问:求占据前90%销售额的商品类型
输出结果:
good_category 1006 1005 1003
表结构:
1 2 3 4 5 6 create table tb_sale_amount( good_category int , sale_date date , amount int , primary key(good_category, sale_date) );
插入数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 insert into tb_sale_amount (good_category, sale_date, amount) values (1003 , '2022-01-10' , 264 ), (1001 , '2022-06-01' , 21 ), (1005 , '2022-06-01' , 73 ), (1002 , '2022-06-27' , 44 ), (1006 , '2022-06-27' , 405 ), (1003 , '2022-09-10' , 16 ), (1005 , '2022-09-13' , 72 ), (1004 , '2022-10-01' , 29 ), (1005 , '2022-10-03' , 332 ), (1001 , '2022-10-29' , 10 ), (1006 , '2022-10-29' , 137 ), (1002 , '2022-12-02' , 23 ), (1007 , '2022-12-02' , 19 ), (1003 , '2022-12-02' , 30 ), (1008 , '2022-12-03' , 3 ), (1009 , '2022-12-04' , 1 ), (1010 , '2022-12-05' , 9 ), (1003 , '2022-12-30' , 121 );
答案
– step1. 计算每种商品的总销售额,并降序排序
1 2 3 4 5 6 select good_category, sum (amount) as good_amountfrom tb_sale_amountgroup by good_categoryorder by good_amount desc ;
– step2. 求全部商品的总销售额,为了step3求各种商品的占比,需要先求和。注意:求总和时,窗口值既不排序也不进行分组
1 2 3 4 5 6 7 8 9 10 11 select * , sum (good_amount) over () as all_amountfrom (select good_category, sum (amount) as good_amount from tb_sale_amount group by good_category order by good_amount desc ) t1;
– step3. 求占比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 select * , good_amount * 1.0 / all_amount as ratiofrom (select * , sum (good_amount) over () as all_amount from (select good_category, sum (amount) as good_amount from tb_sale_amount group by good_category order by good_amount desc ) as t1 ) t2;
– step4. 求累计占比,注意:求累计值时,一定要进行排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 select * , sum (ratio) over (order by ratio desc ) as acc_ratiofrom ( select * , good_amount * 1.0 / all_amount as ratio from (select * , sum (good_amount) over () as all_amount from (select good_category, sum (amount) as good_amount from tb_sale_amount group by good_category order by good_amount desc ) as t1 ) t2 ) t3;
– step5. 求前一行的累计占比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 select * , lag (acc_ratio) over (order by ratio desc ) as pre_acc_ratiofrom (select * , sum (ratio) over (order by ratio desc ) as acc_ratio from ( select * , good_amount * 1.0 / all_amount as ratio from (select * , sum (good_amount) over () as all_amount from (select good_category, sum (amount) as good_amount from tb_sale_amount group by good_category order by good_amount desc ) t1 ) t2 ) t3 ) t4;
– step6. 过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 select * from ( select * , lag (acc_ratio) over (order by ratio desc ) as pre_acc_ratiofrom (select * , sum (ratio) over (order by ratio desc ) as acc_ratio from ( select * , good_amount * 1.0 / all_amount as ratio from (select * , sum (good_amount) over () as all_amount from (select good_category, sum (amount) as good_amount from tb_sale_amount group by good_category order by good_amount desc ) t1 ) t2 ) t3 ) t4 ) t5where pre_acc_ratio IS NULL or pre_acc_ratio < 0.90 ;
关于伪列 教材上有一个案例,其用到的是rownum
1 2 3 4 select * , rownumfrom ddbwhere rownum<= 10
这个语句似乎很好理解,把表里前10行数据拿到了,但为什么下面这个语句拿不到除了第1行以外的数据呢?
1 2 3 4 select * , rownumfrom ddbwhere rownum> 1
rownum的生成是在where之前,可以理解为:数据库把表结果筛好,拿出来,在放到你的眼前时,给每一行加了一个行号
所以上面第一个语句能出结果的原因,是当数据库把第一条记录拿到的时候,给了一个rownum——1,再来看条件:<=10,满足?yes——输出
以此类推,拿到第二条记录,rownum—— 2 —— <=10?——输出。。。一直到第10行
第二个语句:
第一条记录,rownum—— 1 —— >1?——不输出
第二条记录,rownum—— 1(注意这里为什么还是给它1) —— >1?——不输出
其他还可用作练习的题 以下一些是可以使用开窗函数来解决的、之前做过的题,这里受限于篇幅不再详列数据,可根据题目去找一下原题在哪,看开窗的方法能否帮到你,理论上来说,那些需要使用group by的题目,开窗都可以用,只是实现路径不同
问:加油站编号的首位代表加油站所在的区域,每个区域的加油站都有加油记录。基于加油表查询哪些加油卡在全部区域都有加油记录,输出卡号。(阶段测验2 补充几个问题.docx)
问:基于加油卡表查询每天办理的加油卡的卡号是否正常,即卡号的后4位从0001开始,不重复且连续。输出日期和状态(卡号正常、不正常) (阶段测验2 补充几个问题.docx)
问:统计出哪些单位同一年连续3个月都有订购教材,输出单位信息。(阶段练习3.docx)
问:统计出哪些单位连续3个月都没有订购教材,输出单位信息。(阶段练习3.docx)
。。。
小结 1.求解连续问题,开窗函数优势在于:不论连续多少次,都可以解决
2.聚合类开窗,sum,min,max,avg,count等等大部分时候语法不如 普通聚合简洁
3.由于往往需要根据上一次开窗的结果进行筛选、作差等等,开窗一般需要多层嵌套,代码形式上看起来会比普通连接查询复杂,不必刻意追求代码复杂性来炫技
4.开窗更像是一种excel思维,结果比group by 等可以保留更多列
5.开窗函数在解决连续问题上有一定优势,聚合类大部分时候比group by复杂,如果已经能熟练运用连接、group by等,可以把本篇文章讲的内容当作参考
6.写了那么多,其实就只有一点内容,也就是核心作差的那一步,只是为了讲清楚开窗函数,才理了很多概念内容,搬了很多语句,有点为了造出个轮子,把车身车体搬出来造一遍的意思
7.不足之处,请多指正