6000字!SQL窗口函數(shù)詳解。
今天想重提窗口函數(shù)。原因是前幾天在群里提起了這個名字,忘了是什么緣由提起的,但令我吃驚的是,竟還有同學想從事數(shù)據分析卻不知道窗口函數(shù)!那感覺就仿佛用勺子吃面條不知道有筷子這種好東西一樣。
他問:“有沒有大佬簡單說下窗口函數(shù)是個什么東西?”
胖里:不減少原表行數(shù)情況下,對數(shù)據進行分組排序。
阿鑫:在SQL中窗口函數(shù)可以對數(shù)據進行同步處理,where和group by處理后進行操作,只能寫在select子句中。
其他:百度吧,知乎看一下。
然后我去翻了翻公眾號之前關于窗口函數(shù)的文章,我以為自己對這部分內容已經了解和解釋的挺清楚了,無論是用法還是實例。但我翻完才發(fā)現(xiàn),好像并沒有對窗口函數(shù)下定義,其他人的很多文章也大都沒對窗口函數(shù)的定義進行描述,都從窗口函數(shù)有什么用,怎么用,舉例子開始。
窗口函數(shù)到底是什么呢?
01
窗口函數(shù)是什么
我拿這個問題去問交流群里的小伙伴,得到了如下一些回答。
A:處理分析的函數(shù),類似于聚合函數(shù)?
B:用于解決組內排序、聚合等運算且只能寫在select字句中的函數(shù)。
C:窗口函數(shù)可以切分小組,并在小組內實現(xiàn)排序、聚合等數(shù)據處理操作。
D:窗口函數(shù)的含義就像它的名字,開一個窗子,在不影響房屋原來結構的基礎上從自己想要的角度觀察內部關系。
E:窗口函數(shù)類似于聚合函數(shù)。區(qū)別是聚合函數(shù)對每組只返回一個值,窗口函數(shù)返回多個值,也就是說對組里的每條記錄都會產生返回值~
每個人都講出了自己的理解,每個人理解的也都沒什么問題。在我看來,窗口函數(shù)無非就是一種略高級的操作,能劃分范圍(組),對這一范圍內的數(shù)據進行某種處理,可以是聚合,可以是排序、也可以是求第一個記錄、最后一個記錄等。它有其高級之處,也有用法上的某些限制。
窗口函數(shù),也稱為OLAP(Online Analytical Processing)函數(shù),之所以叫窗口函數(shù)是為了便于形成直觀印象,易于理解(雖然可能對一些同學而言,并沒有那么容易理解。)
如上便是窗口函數(shù)的定義,要想理解窗口函數(shù)從定義上是遠遠不夠的,最好能從窗口函數(shù)的語法及其實例來理解。

上圖便是窗口函數(shù)的語法了,你可以將其看作一種固定搭配,填充進去不同的窗口函數(shù)、指定列,即可實現(xiàn)對應函數(shù)能實現(xiàn)的操作。
這其中最重要的關鍵字便是partition by和order by,partition by用來圈定所要進行操作的對象的范圍,order by用來指定按照哪列、何種順序進行排序。通過partition by分組后的記錄集合叫做窗口,此處的窗口表示范圍,這也是窗口函數(shù)名字的由來。
但partition by并非必需,不使用partition by也可正常使用窗口函數(shù),只不過此時等于不分組,將整張表作為一個大窗口而已。
02
窗口函數(shù)的分類
說完窗口函數(shù)的語法,不妨來看看常用/常見的一些窗口函數(shù)及其分類。
下圖中羅列了三種窗口函數(shù)的分類,其實分類這種東西,都是主觀上按照某些客觀規(guī)則劃分的,劃分的人不一樣也就形成了不同或不同粒度的規(guī)則。
窗口函數(shù)可歸為兩大類,聚合類窗口函數(shù)和非聚合類窗口函數(shù)(也可稱為專用窗口函數(shù))。聚合類窗口函數(shù)是將我們常用的聚合函數(shù)作為窗口函數(shù)使用,非聚合類窗口函數(shù)指一些規(guī)定好的窗口函數(shù),按照其不同的功能進行定義和劃分,如常見的排序函數(shù)、分布函數(shù)和偏移函數(shù)。
注意,有的窗口函數(shù)是帶參數(shù)的,有的不帶參數(shù),如sum(amt)、lead(time)、rank()、row_number()。



其實我們可能并不需要知道所有的窗口函數(shù),我這水平目前也只知道聚合、排序、返回值的幾個函數(shù),一些是因為面試??妓麄兊南嗤筒煌c,一些是業(yè)務或筆試題中會用得到。這些函數(shù)基本懂一部分就夠用了,其他需要的可以根據需求再進行查詢、學習和使用。
03
窗口函數(shù)的應用
籠統(tǒng)地介紹完一些常見的窗口函數(shù),還是好好說說其中一些函數(shù)的用法吧。
先來幾個簡單的示例,初步了解下窗口函數(shù)的使用。
下表為某公司在各地區(qū)不同月份的銷售額記錄表,sales_table(虛構數(shù)據)。

題目1:求取每個地區(qū)的銷售額(以表內所示月份為例)
select?area,sum(sale_amt)?--?每地區(qū)總銷售額from?sales_tablegroup?by?area;
這個題目算是在學習SQL基礎的時候比較簡單且常見的題目了吧,毫無難度的那種,求某個分組的和,group by,sum()一下就OK了。
題目2:求每個地區(qū)每月銷售額占比,以及月累積銷售額占比
按之前分組求sum(),只能得到每個地區(qū)對應的總銷售額,分別為1500000、870000、1640000,而每個地區(qū)每月銷售額占比就得實現(xiàn)500000/1500000,450000/1500000等操作了,此結果也可通過先求每地區(qū)的總銷售額然后表關聯(lián)進行字段間的除法操作,不過有了窗口函數(shù),一切便變得簡單了些。
select?*,sale_amt/sum_amt?as?sale_amt_ratio?--?每地區(qū)每月銷售額占比from(??select?*,sum(sale_amt)?over(partition?by?area?order?by?area)?as?sum_amt???from?sales_table)as?t?
用sum()聚合函數(shù)作為窗口函數(shù),使用partition by area將地區(qū)作為分組,在地區(qū)內求得每個地區(qū)的總銷售額,此處order by由于語法原因,不可缺,但由于分組后的地區(qū)只有自己本身這個地區(qū),所以此處的order by無實際意義。
結果如下所示,求得了每個地區(qū)每月銷售額占比:

那累積銷售額占比呢?還是可以用sum()聚合函數(shù)做窗口函數(shù),分組依舊是按照地區(qū)進行分組,但累積銷售額就牽扯到時間先后了,因此要學會善用order by對日期進行排序,才能求得真正時間順序上的累積銷售額。
select?*,agg_amt/sum_amt?as?agg_sale_amt_ratio?--?每地區(qū)月累積銷售額占比from(??select?*????,sum(sale_amt)?over(partition?by?area?order?by?sale_time)?as?agg_amt?--?每地區(qū)月累積銷售額????,sum(sale_amt)?over(partition?by?area?order?by?area)?as?sum_amt?--?每地區(qū)總銷售額??from?sales_table)as t

經過上述這番操作,應該初步認識到窗口函數(shù)所謂的高級及實用之處了吧,無非就是將原先需要各種分別求取再join的操作,通過窗口函數(shù)的應用在多個子查詢中實現(xiàn)。上述的兩個sum()窗口函數(shù)能同時求取,放在一個子查詢內,也就是說一個select子句中可有多個窗口函數(shù)。
注意:求累積的時候不僅可以實現(xiàn)上述的按月累積,也可實現(xiàn)限定的按前/后/前后幾個月累積,畢竟有時候有些需求是從頭到尾累積,而有的是要求前后共三個月的累積。
這時候用來在窗口中指定更加詳細匯總范圍的功能便出現(xiàn)了,此功能中的匯總范圍叫做框架,使用方法就是在order by子句后使用用來指定范圍的關鍵字following和preceding。
舉個小例子。
select?*????,sum(sale_amt)?over(partition?by?area?order?by?sale_time?rows?between?1?preceding?and?1?following)?as?agg_amt?--?當前行及其前一行和后一行from?sales_table;select?*????,sum(sale_amt)?over(partition?by?area?order?by?sale_time?rows?1?preceding)?as?agg_amt?--?當前行及其前一行from?sales_table;--?mysql單獨運行following時報錯,未解,但可使用rows between 0 preceding and 1 following來替代select?*????,sum(sale_amt)?over(partition?by?area?order?by?sale_time?rows?1?following)?as?agg_amt?--?當前行及其后一行from?sales_table;

上述例子即為指定更詳細的匯總范圍了,preceding指前多少行,following指后多少行,然后再加上當前的行,如rows between 1 preceding and 1 following 表示前一行、后一行加當前行,共三行。
題目3:求每個地區(qū)各個月銷售額排名情況
說到排名,應該能想到排序吧,如果沒接觸過窗口函數(shù)想要比較大小,你們都會用什么方法呢?對于排序,我首先想到的就是order by了,order by不就是用來排序的嗎?能實現(xiàn)順序排列,但只是不打標,記錄數(shù)太多可能就不知道有多少名了,除此之外我滿腦子都是各種表的關聯(lián)、對比。
不想那么復雜的話,就試試窗口函數(shù)中用于排序的函數(shù)吧。說到排序的窗口函數(shù),不少人應該都知道rank(),dense_rank(),row_number(),除了在筆試題中考到用他們來排序,面試也常問這三者的區(qū)別是什么。
select?*,row_number()?over(partition?by?area?order?by?sale_amt?desc)?as?rnk?from?sales_table;

使用窗口函數(shù),輕松實現(xiàn)按地區(qū)分組,對每個月的銷售額進行排序打標,一目了然,不僅可以排序,設想一下這種情況,你們公司的數(shù)據庫表里不僅只有這10條數(shù)據,而是有成百上千甚至上萬條數(shù)據,老板讓你看看每個地區(qū)銷售額最高的2個月份是哪兩個,你總不至于分別求取吧,一個排序窗口函數(shù)就能解決的問題,何樂而不為?
此處我只用了我最常用的row_number()來實現(xiàn),針對不同的排序要求,可選擇不一樣的排序函數(shù),這三種排序函數(shù)不太清楚的自己回去反思吧。
有一張表log_table,其中包括用戶id(user_id)及訪問時間(visit_time)兩個字段。
題目4:每天隨機取1000個用戶
select?user_id,visit_timefrom(??select?user_id,visit_time,row_number() over(partition by visit_time order by rand()) as rnk??from?log_table?)as twhere?rnk?<=?1000;
當然,這個題目,不使用窗口函數(shù)依舊也可以通過order by rand()實現(xiàn)。
題目5:每天隨機取10%個用戶
select?user_id,visit_timefrom(??select?user_id,visit_time????????,percent_rank()?over(partition?by?visit_time?order?by?rand())?as?rnk_ratio??from?log_table?)as twhere rnk_ratio?<=?0.1;
當然,這個題目也可以不用窗口函數(shù),通過計算用戶總數(shù),再隨機取用戶總數(shù)的10%。
不盡興的話,可以再來一張用戶表user_table,里面存儲了某平臺某天的活躍用戶user_id,且不重復。
題目6:將用戶隨機分成10組,每組取1000個用戶。
select?user_id?from(??select?user_id,n_rnk????????,row_number()?over(partition?by?n_rnk?order?by?rand())?as?rnkfrom(????select?user_id???????????,ntile(10)?over(order?by?rand())?as?n_rnk?????from?user_table??)as?t1?)as t2where?rnk?<=?1000;
隨機分十組,用窗口函數(shù)的ntile()可以實現(xiàn)分桶(分組功能),再使用一次排序,隨機從每組中選出1000個用戶。
題目7:將用戶隨機分成100組,每組取10%個用戶
這個題目的答案就顯而易見了吧。
上述幾個簡單的示例是為了說明窗口函數(shù)的用法及其意義所在,使用窗口函數(shù)可能會使解決問題的過程變得更簡單,對窗口函數(shù)的熟悉和理解可以幫助我們在之后遇到問題的時候,雖然是取數(shù),但可以更有針對性、更高效地取數(shù)。
除上述一些簡單示例外,筆面試中常考的需要用窗口函數(shù)解決的問題一般包括topn問題、連續(xù)登錄問題。這兩種題目面試中問到的頻率特別高,前幾天群里有個小伙伴就說被問到了,但一時想不出如何解。
想來還是對窗口函數(shù)不熟悉吧,哪一類的問題涉及到什么關鍵字,應該用哪種函數(shù)解決,這些雖然在各種文章中頻被提及,但你如何理解,是否真正理解且能應用,就是個人的問題了。
這些題目我就不多提了,之前轉的寶器的這篇文章中早就講過一遍了,文章在這:解一下TMD幾道熱門數(shù)據分析面試題。

推薦閱讀
歡迎長按掃碼關注「數(shù)據管道」
