SQL之視窗函式
一、視窗函式介紹
1 視窗函式語法
<視窗函式> over (partition by <用於分組的列名> order by <用於排序的列名>)
- 專用視窗函式,比如rank,dense_rank,row_number等
- 聚合函式,如sum,avg,count,max,min等
2 視窗函式功能
- 不減少原表的行數,所以經常用來在每組內排名
- 同時具有分組(partition by)和排序(order by)的功能
3 視窗函式使用場景
業務需求“在每組內排名”,比如:
- 排名問題:每個部門按業績來排名
- topN問題:找出每個部門排名前N的員工進行獎勵
4 注意事項
- 視窗函式原則上只能寫在select子句中
- partition by 子句可以省略,省略就是不指定分組,但是這就失去了視窗函式的功能,所以一般不要這麼使用
5 group by、order by 子句與視窗函式的區別
group by 分組彙總後改變了表的行數,一行只有一個類別,而partition by和rank函式不會減少原表中的行數
二、標準聚合函式
標準的聚合函式有avg、count、sum、max和min,接下來分別介紹這些聚合函式的視窗函式形式。
1 移動平均視窗函式
移動平均值的定義:若依次得到測定值(x1,x2,x3,...,xn)時,按順序取一定個數所做的全部算數平均值。例如(x1+x2+x3)/3,(x2+x3+x4)/3,(x3+x4+x5)/3,...就是移動平均值。其中,x可以是日或者月,以上的可以成為3日移動平均,或3月移動平均,常用於股票分析中
#語法結構 avg(欄位名) over( partition by 用於分組的列名 order by 用於排序的列名 asc/desc rows between A and B)
#A和B是計算的行數範圍
rows between 2 preceding and current row #取當前行和前面兩行
rows between unbounded preceding and current row #包括本行和之前所有的行
rows between current row and unbounded following #包括本行和之前所有的行
rows between 3 preceding and current row #包括本行和前面三行
rows between 3 preceing and 1 following #從前面三行和下面一行,總共五行
當order by後面缺少視窗從句條件,視窗規範預設是 rows between unbounded prceding and current row.
當order by 和視窗從句都缺少,視窗規範預設是 rows between unbounded preceing and unbounded following
例子
select *,avg(grade) over( order by stu_no rows between 2 preceding and current row ) as '三移動平均' from v_info
2 計數(count)視窗函式
視窗函式count(*) over()對於查詢返回的每一行,它返回了表中所有行的計數
語法結構: count(欄位名1) over( partition by 欄位名2 order by 欄位名3 asc/desc)
例子1
查詢出成績在90分以上的人數
select *, count(*) over() as 'ct' from v_info where grade>=90
例子2
按照課程號進行分組,找出成績大於等於80分的學生人數
select *, count(*) over(partition by c_no) as 'ct' from v_info where grade>=80
吐槽:這兩個例子舉得不是很好,如果只是為了看學生人數,用group by 能更明瞭地看,應該說找出大於等於80分的學生人數及其相關資訊。
3 累計求和(sum)視窗函式
語法結構: sum(欄位名1) over ( partition by 欄位名2 order by 欄位名3 asc/desc) #按照欄位1進行累計求和,按照欄位2進行分組,在組內按照欄位3進行排序
例子 1
根據學號排序,對學生的成績進行累計求和
select *, sum(grade) over(order by stu_no) as '累計求和' from v_info
例子 2
按照課程號分組,然後根據學號對成績進行累計求和
select *,sum(grade) over( partition by c_no order by stu_no ) as '累計求和' from v_info
注:一定要選擇根據學號排序,要不然的出來的是最終的累計求和結果,如下圖:
select *, sum(grade) over( partition by c_no) as '累計求和' from v_info
4 最大(max)、最小值(min)視窗函式
語法結構 max(欄位名1) over(patition by 欄位名2 order by 欄位名3 asc/desc) min(欄位名1) over(patition by 欄位名2 order by 欄位名3 asc/desc)
例子 1
求成績的累計最大值和累計最小值
例子2
按照課程號進行分組,再求最大、最小值
select *, max(grade) over(partition by c_no order by stu_no) as '累積最大值', min(grade) over(partition by c_no order by stu_no) as '累積最小值' from v_info
例子3
根據學生號和課程號求成績的累積最小值
select stu_no,c_no,stu_name,sex,birth,grade, min(grade) over(partition by stu_no,c_no) as '累積最小值' from v_info
例子 4
統計2019年10月1日-10月10日每天做新題的人的數量,重點在每天。
- 這個題的重點是在每天,所以需要求出count(時間) = 10的使用者ID;
- 這個題可以使用min() over()視窗函式,先根據每個做題者和試卷號,找出每個做題者的最小日期,這裡和前面(3)的解題思路是一樣的
- 如果每天都做題,那麼得到的日期是不一樣的,所以count(時間)會等於10
- 再對這部分的使用者ID進行求和,就可以找出每天都做新題的人了。
select count(a.sno) as '每天做題的人數' from (select sno,s_id,time, min(time) over(partition by sno,s_id) as 'first_time' from paper where date_format(time,'%Y-%m-%d' ) between '2019-10-01' and '2019-10-10') as a where a.time = a.first_time group by sno having count(distinct a.first_time)=10
三、排序視窗函式
row_number()、rank()、dense_rank(),這三個函式的作用都是返回相應規則的排序序號。
1 row_number()
為查詢出來的每一行記錄都會生成一個序號,依次排序且不會重複。1,2,3,4
語法:
row_number() over(partition by 欄位1 order by 欄位2) #欄位1是分組的欄位名稱
2 rank()
使用rank函式來生成序號,over子句中排序欄位值相同的序號是一樣的,後面欄位值不相同的序號將跳過相同的排名排下一個,rank函式生成的序號有可能是不連續的,即排名可能為1,1,3,是跳躍式排名,有兩個第一名時接下來就是第三名。1,1,1,4
語法: rank() over(partition by 欄位1 order by 欄位2)
3 dense_rank()
dense_rank函式在生成序號時是連續的,當出現相同排名時,將不跳過相同排名號,有兩個第一名時仍跟著第二名,即排名為1,1,2這種。1,1,1,2.
語法: dense_rank() over(partition by 欄位1 order by 欄位2)
注:在上述的三個排序專用視窗函式中,函式後面的括號不需要任何引數,保持()空著就可以。
四、分組排序視窗函式
可以按照銷售額的高低、點選次數的高低,以及成績的高低為對使用者和學生進行分組,這裡的考點是:取銷售額最高的25%的使用者(將使用者分成4組,取出第一組)、取成績高的前10%的學生(將學生分成10組,取出第一組)等等。
語法結構: ntile(n) over(partition by 欄位名2 order by 欄位名3 asc/desc)
#n表示要切片的分數,如需要取前25%的使用者,則需要分4組,取前10%的使用者,則需要分10組
- ntile(n),用於將分組資料按照順序切分成n片,返回當前切片值
- ntile不支援rows between 的用法
- 切片如果不均勻,預設增加第一個切片的分佈
例子 1
取出成績前25%的學生資訊
- 第一步:按照成績的高低,將學生按照成績進行切片
select *, ntile(4) over(order by grade desc) as 'rank' from v_info
- 第二步:按照rank篩選出第一組,則得到最終的結果如下:
select a.* from (select *, ntile(4) over(order by grade desc ) as 'rank' from v_info) as a where a.rank=1
五、偏移分析視窗函式
- lag() over()和lead() over()視窗函式,lag和lead分析函式可以在同一次查詢中取出同一個欄位的前N行資料(lag)和後N行(lead)作為獨立的列。
- 在實際應用當中,若要用到取今天和昨天的某欄位的差值時,lag和lead函式的應用就顯得尤為重要了
- 適用場景:獲取使用者在某個頁面停留的起始與結束時間
#語法結構 lag(exp_str,offset,defval) over(partition by ... order by ..) lead(exp_str,offset,defval) over(partition by ... order by..) #exp_str 表示欄位名稱 #offset偏移量,假設當前行在表中排在第5行,offset為3,則表示我們所要找的資料行就是表中的第2行(即5-3=2) #offset預設為1
例子 1
向前推1個日期
select *, LAG(birth,1,0) over(partition by sex) as 'lag_1' from v_info
例子 2
向後推1個日期
select *, lead(birth,1,'無') over(partition by sex) as 'lead_1' from v_info
例子 3
統計每天符合以下條件的使用者數:A操作之後是B操作,AB操作必須相鄰。
使用者行為表racking_log(user_id,operate_id,log_time)