运用开窗函数解决“连续”问题

本文最后更新于 2024年11月13日 凌晨

学习数据库的过程中,练习题目的时候我们经常会遇到如下等问题:

查询连续三个月都投稿的作者姓名?——期刊表练习题

查询同年连续三个月都有违法记录的当事人信息?——交通执法练习题

用户登录表中连续登录了三次及以上的用户?

查询产品号存在断号的情况、及断开的号码?

可以看到,以上这些问题似乎都与“连续”相关,又各有不同,例如,第一问仅求解连续三个月有投稿记录,那么如下表格中,当我们把日期升序排序后,第1到3行是满足的,4到5行不满足:

时间序号
2023-01-031
2023-02-112
2023-03-223
2023-05-241
2023-06-102

第二问在此基础上多加了一个“同年”,那么如下表格中只有第3行到5行是满足要求的,类似地,题目还可以换为“同一天连续n个小时”、“同一月连续n天”,甚至两个、三个条件联立,如“同一年同一月同一小时连续n秒”,这里仅以同年连续3个月举例,观察下表的序号与第一个表格有什么区别:

时间序号
2022-01-031
2022-02-042
2023-03-081
2023-04-022
2023-05-013
2023-07-091

这就对我们产生了启发,我如果能使用教材上简单介绍的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_num
FROM DEMO;

image-20231101191245161

以下语句、及结果:

加了partition by以后,可以看到,结果发生变化,按组求和,并把同一组放在一起给你显示,可以看到,每一行对应的这个开窗值,都是与该组的“属性”相关的,这个属性指的就是:它们之所以都一样,是因为它们同属于一个id,即同一个partition by,因此当使用下面第二个代码块的语句(使用distinct,使用内嵌视图)就会使结果和普通聚合一模一样。

1
2
3
SELECT *,
SUM(num) OVER(PARTITION BY ID) sum_num
FROM DEMO;

image-20231102071248700

1
2
3
4
5
6
7
SELECT DISTINCT ID,
sum_num
FROM (
SELECT *,
SUM(num) OVER(PARTITION BY ID) sum_num
FROM DEMO
);

image-20231102071337708

请自行观察下列语句的执行结果(acc_num取名 accumulate的意思):

1
2
3
SELECT *,
SUM(num) OVER(ORDER BY NUM) acc_num
FROM 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_num
FROM DEMO;

image-20231102075000825

偏移

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_num
FROM DEMO;

image-20231102080236851

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_num
FROM DEMO;

image-20231102080417481

可以看到,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_numxx
FROM 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) rn
FROM TGB

image-20231102152006206

可以看到,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) delta
FROM (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY zzh,YEAR(TGRQ) ORDER BY TGRQ) rn
FROM TGB
)

image-20231102170713914

再来关注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;

image-20231102171604058

为什么最后一步不开窗?数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;

image-20231102173820356

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并导入你的数据库:

文件名链接提取码
ddb.xlsxhttps://pan.baidu.com/s/1HzdiUc8YV8Eu7ZvaRmc3kw6666

问:订单表中的订单号应该是连续编号,找出订单表中的订单号是否有重号?重号是什么?

重号问题,使用开窗函数来做的话,最先想到的是用dense_rank(),因为这个排序函数会让两个相等的值获得一样的排名,观察这个语句在发生重号处的结果:

1
2
3
SELECT ddh,
DENSE_RANK() OVER(ORDER BY ddh::INT) rn
FROM DDB

image-20231121184320025

可以看到,只需要把第二列排序结果里出现两次及以上的筛选出来,就得到了发生重号的地方

直接贴上完整的查重语句:

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 DDH
HAVING COUNT(*)>=2;

image-20230921183634898

可以想见,本题里最多只出现了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_ddh
FROM (
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)) a
LEFT JOIN
(SELECT DDH::INT FROM DDB) b
ON a.num=b.ddh
WHERE 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_empty
FROM (
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;

image-20231121224102041

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_amount
from tb_sale_amount
group by good_category
order by good_amount desc;

– step2. 求全部商品的总销售额,为了step3求各种商品的占比,需要先求和。注意:求总和时,窗口值既不排序也不进行分组

1
2
3
4
5
6
7
8
9
10
11
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;

– step3. 求占比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;

– 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_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
) 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_ratio
from(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_ratio
from(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
) t5
where pre_acc_ratio IS NULL or pre_acc_ratio < 0.90;

关于伪列

教材上有一个案例,其用到的是rownum

1
2
3
4
select *,
rownum
from ddb
where rownum<=10

这个语句似乎很好理解,把表里前10行数据拿到了,但为什么下面这个语句拿不到除了第1行以外的数据呢?

1
2
3
4
select *,
rownum
from ddb
where 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.不足之处,请多指正


运用开窗函数解决“连续”问题
https://inkcodes.com/2023/11/21/运用开窗函数解决“连续”问题/
作者
Specialhua
发布于
2023年11月21日
更新于
2024年11月13日
许可协议