通俗易懂:視窗函式 | 全是案例
什麼是視窗函式
相信很多人都比較熟悉 SQL 聚合函式的語法,比如 count(), sum(), max()等,
視窗函式類似聚合函式,不同的是視窗函式不改變原有的行。
視窗函式是資料分析和資料開發必備的技能。
基本語法:<視窗函式> over (partition by <用於分組的列名> order by <用於排序的列名>)
可能這樣的解釋還是不明瞭,沒事,往後看,一會你就明白了。
案例
現在先模擬幾條資料,假如目前有學生成績表(stu_scores)如下:
class(班級) | id(學號) | score(成績) |
---|---|---|
1 | 004 | 71 |
2 | 003 | 98 |
1 | 002 | 98 |
2 | 001 | 80 |
2 | 005 | 77 |
1 | 006 | 80 |
語句一:
select *,
rank①() over②(partition by③ class order by④ score desc) ranking
from stu_scores;
得到的結果如下:
class(班級) | id(學號) | score(成績) | ranking |
---|---|---|---|
1 | 002 | 98 | 1 |
1 | 006 | 80 | 2 |
1 | 004 | 71 | 3 |
2 | 003 | 98 | 1 |
2 | 001 | 80 | 2 |
2 | 005 | 77 | 3 |
接下來解釋一下這段 SQL,這條 SQL 的目的是求每個班級內的成績排名
① rank() 排序的函式
② over() 指定分析函式工作的資料視窗大小
③ partition by 指定分組欄位,這個案例中用 class 作為分組欄位, 類似 group by
④ order by 排序,對分組後的結果進行排序
可能有些朋友會問:“這不就是 group by 和 order by 的用法麼?不用視窗函式也能實現,為啥要用它?”
這是因為,單純使用 group by 分組彙總後改變了表的行數,一行只有一個類別;
而使用視窗函式則不改變行數,可以將詳細資訊也展示出來。
到這,應該大致明白視窗函式的使用場景及如何使用了吧。
為了讓大家更好地理解視窗函式,再寫幾條語句,看看結果是否和你想的一致。
語句二:
select *,
sum(score) over(order by id) as win_sum,
count(score) over(order by id) as win_count,
min(score) over(order by id) as win_min
from stu_scores;
結果:
class | id | score | win_sum | win_count | win_min |
---|---|---|---|---|---|
2 | 001 | 80 | 80 | 1 | 80 |
1 | 002 | 98 | 178 | 2 | 80 |
2 | 003 | 98 | 276 | 3 | 80 |
1 | 004 | 71 | 347 | 4 | 71 |
2 | 005 | 77 | 424 | 5 | 71 |
1 | 006 | 80 | 504 | 6 | 71 |
這樣的結果是否和你想的一樣呢?
由於不加 partition by 因此沒有分組,所以從第一行開始開窗做計算。
以 win_sum 為例,第一行成績相加 80,與第二行相加得 178,再與第三行相加得 276,以此類推。
這樣做有什麼意義呢?
可以每一行的資料裡直觀的看到,截止到本行資料,統計資料是多少。
同時可以看出每一行資料,對整體統計資料的影響。
從成績上可能不太好理解,如果是從生產經營角度,比如對比每月營業額,可以更直觀地看出差距。
其它視窗函式及關鍵字
rank 與 dense_rank
rank(), dense_rank() 都屬於排序函式,區別在於有重複資料的時候如何排,看案例就知道
select *,
rank() over(order by score desc) as ranking,
dense_rank() over(order by score desc) as dense_ranking
from stu_scores;
結果:
class | id | score | ranking | dense_ranking |
---|---|---|---|---|
1 | 002 | 98 | 1 | 1 |
2 | 003 | 98 | 1 | 1 |
2 | 001 | 80 | 3 | 2 |
1 | 006 | 80 | 4 | 3 |
1 | 004 | 71 | 5 | 4 |
2 | 005 | 77 | 6 | 5 |
可以看到,遇到重複排名的時候,rank 是跳躍排序,如果有兩個第一,那接下來是第三;
dense_rank()則是連續排序,如果有兩個第一時,那接下來是第二。
lead 與 lag
lead(col, n, default_val):用於統計視窗內往下第 n 行值。
第一個引數為列名,第二個引數為往下第 n 行(可選,預設為1),第三個引數為預設值(當往下第n行為NULL時候,取預設值,如不指定,則為NULL。
lag(col,n, default_val):用於統計視窗內往上第n行值,引數和 lead 一樣
還是看案例吧,用文字講確實很難講清楚
問題:根據班級分組,統計每個班學生的成績以及小於(大於)等於該學生成績的上(下)一個學生的成績:
select *,
lead(score,1) over(partition by class order by score) as lead,
lag(score,1) over(partition by class order by score) as lag
from stu_scores;
結果:
class | id | score | lead | lag |
---|---|---|---|---|
1 | 004 | 71 | 80 | null |
1 | 006 | 80 | 98 | 71 |
1 | 002 | 98 | null | 80 |
2 | 005 | 77 | 80 | null |
2 | 001 | 80 | 98 | 77 |
2 | 003 | 98 | null | 80 |
可以看到,第二行 lead 的結果是第三行的成績,lag 的結果是第一行的成績,沒有的則為 null
last_value 與 first_value
這兩個比較簡單,顧名思義,分別表示取視窗內的最後一個值和第一條資料,但是先看看例子
select *,
first_value(score) over(partition by class order by score) as first,
last_value(score) over(partition by class order by score) as last
from stu_scores;
結果:
class | id | score | first | last |
---|---|---|---|---|
1 | 004 | 71 | 71 | 71 |
1 | 006 | 80 | 71 | 80 |
1 | 002 | 98 | 71 | 98 |
2 | 005 | 77 | 77 | 77 |
2 | 001 | 80 | 77 | 80 |
2 | 003 | 98 | 77 | 98 |
從結果看,first_value 的結果很合理,是每個分割槽的第一個資料;
但 last_value 的結果好像不符合期望,這個和我接下來要說的幾個關鍵字有關。
UNBOUNDED、PRECEDING、FOLLOWING、CURRENT ROW
先粗略地解釋一下這些關鍵字:
CURRENT ROW:當前行
n PRECEDING:往前 n 行資料
n FOLLOWING:往後 n 行資料
UNBOUNDED:起點
-
UNBOUNDED PRECEDING 表示從前面的起點,
-
UNBOUNDED FOLLOWING 表示到後面的終點
老規矩,先看例子
SELECT *,
last_value(score) ov.r(PARTITION BY class ORDER BY score) last1,
last_value(score) over(PARTITION BY class ORDER BY score RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT row) last2,
last_value(score) over(PARTITION BY class ORDER BY score RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED following) last3,
last_value(score) over(PARTITION BY class ORDER BY score RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING) last4,
last_value(score) over(PARTITION BY class ORDER BY score ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) last5
FROM stu_scores;
結果:
class | id | score | last1 | last2 | last3 | last4 | last5 |
---|---|---|---|---|---|---|---|
1 | 004 | 71 | 71 | 71 | 98 | 71 | 80 |
1 | 006 | 80 | 80 | 80 | 98 | 80 | 98 |
1 | 002 | 98 | 98 | 98 | 98 | 98 | 98 |
2 | 005 | 77 | 77 | 77 | 98 | 80 | 80 |
2 | 001 | 80 | 80 | 80 | 98 | 80 | 98 |
2 | 003 | 98 | 98 | 98 | 98 | 98 | 98 |
用的都是 last_value 結果還不一樣,為什麼呢?
這是因為,last_value 預設的視窗是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,
表示當前行永遠是最後一個值,因此 last1 和 last2 的結果是一樣的。
如果要獲取每個分組的最後一個值,
則需改成 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,
表示從最前一行作為起點,最後一行為終點,就是 last3 的結果(由於兩個分組排序後最後一個數都是 98,看不出區別,大家可以去驗證一下)
至於 last4 和 last5 的區別,則是 RANGE 和 ROWS 的區別:
RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING:
表示當前行的值分別減 3 和 加 3,以第 4 行為例,原來的 score 是 77,各加減 3,則是 74 到 80 的範圍
80 剛好是下一行的值,因此它的結果為 80,其它行由於加減 3 後沒有對應的值,因此為自身。
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:
表示當前行分別往前往後減一行,記住,rows 和 range 的區別就在於是當前行數還是當前行的值。
因此在同一個分組內,第一行 last5 的值為下一行的值,第二行為下一行的值,以此類推,第二個分組也一樣。
總結
到這,視窗函式的內容就基本上都講完了。這些可以說是資料分析和資料開發必備的技能,因此必須要熟練。
至於如何才能熟練,還需要多實踐。
碼字不易,如果覺得不錯,麻煩動動小手點個贊,謝謝!
持續關注不迷路,轉載請註明出處!—— 大資料的奇妙冒險