Hive窗口函数进阶指南
作為一名數(shù)據(jù)小哥,在寫SQL的漫漫路上,窗口函數(shù)猶如一把披荊斬棘的利劍,幫助作者解決了很多繁瑣復(fù)雜的需求,在此對窗口函數(shù)表示感謝。
本文在介紹了窗口函數(shù)的同時(shí),著重介紹Hive窗口函數(shù)的使用,希望讀者在看完本篇文章之后,對窗口函數(shù)的使用能夠有所掌握。
值得注意的是本文中的例子使用的是HQL(Hive SQL),本文需要一定的SQL基礎(chǔ),如果想了解基礎(chǔ)SQL,請移步本人的數(shù)據(jù)分析師之快速掌握SQL基礎(chǔ) 。
兩個(gè)問題
對于數(shù)據(jù)工作者來說,窗口函數(shù)或多或少都使用過,但是可能沒有系統(tǒng)的去總結(jié)它的用法。
如果讀者對于窗口函數(shù)有一點(diǎn)了解的話,不妨先看看針對下表的兩個(gè)問題,如何使用SQL去解決;如果讀者對于窗口函數(shù)一點(diǎn)都不了解,那請您直接跳過這一部分,直接從什么是窗口函數(shù)開始閱讀。
針對上面一張學(xué)生成績表(class),有year-學(xué)年,class-課程,student-學(xué)生,score-分?jǐn)?shù)這四個(gè)字段,請看問題:
問題1:每年每門學(xué)科排名第一的學(xué)生是?
問題2:每年總成績都有所提升的學(xué)生是?
對于問題1來說比較簡單,既可以使用聚合函數(shù)來統(tǒng)計(jì),也可以使用窗口函數(shù)來統(tǒng)計(jì),其中窗口函數(shù)給了兩種解法:
--使用聚合函數(shù) select?a.year,a.class,b.student from ( select?year,class,max(score)?as?max_score from?class group?by?year,class )?a?join?class?b? on?a.year?=?b.year?and?a.class?=?b.class? and?a.max_score?=?b.score order by a.year執(zhí)行結(jié)果如下,如果有相同成績的話都會保留。
--使用窗口函數(shù)max select?a.year,a.class,a.student from ( select?year,class,score,student ,max(score)?over? (partition?by?year,class)?as?max_score? --增加一列為聚合后的最高分from?`class` )?a? where a.score = max_score --保留與最高分相同的記錄數(shù)執(zhí)行結(jié)果如下,同樣的如果有相同記錄也會保留下來。
--使用窗口函數(shù)first_value select?distinct?year,class ,first_value(student)?over? (partition?by?year,class? order?by?score?desc)?as?student from class執(zhí)行結(jié)果,需要注意的是如果有相同成績,只會取一條記錄。
對比兩種寫法可以發(fā)現(xiàn):
? 使用窗口函數(shù)的SQL代碼量少
? 避免了與原表的join
對于問題2,是一個(gè)相對復(fù)雜但是比較常見的需求,無法只使用聚合函數(shù)來統(tǒng)計(jì),只能配合窗口函數(shù)來統(tǒng)計(jì)。
select student from (select year,student,if((sum_score - lag(sum_score,1,0) over (partition by student order by year))?>?0,1,0)?as?flag,(sum_score - lag(sum_score,1,0) over (partition by student order by year)) as flag1--按照student進(jìn)行分區(qū)并進(jìn)行year正序排序--,找到每個(gè)學(xué)生的上一條學(xué)年總成績--,并與當(dāng)年成績相減,如果小于--,則將flag值置為1,否則置為0from(select year,student,sum(score) as sum_score --按照學(xué)年和學(xué)生進(jìn)行成績匯總from classgroup by year,student) a ) b group by student having avg(flag) = 1 --平均值為1則代表是每年都有增長執(zhí)行結(jié)果:
通過上面兩個(gè)問題,可以對窗口函數(shù)的特征做一個(gè)簡單的小結(jié):
? 聚合函數(shù)可以作為窗口函數(shù)使用
? 具有計(jì)算和取值的功能
? 不改變記錄數(shù)
什么是窗口函數(shù)
相信看了上面的兩個(gè)問題后,對窗口函數(shù)的使用有一個(gè)大概的了解。下面從理論方面來詳細(xì)了解下窗口函數(shù)。
理論
窗口函數(shù)也稱為OLAP(Online Analytical Processing)函數(shù),是對一組值進(jìn)行操作,不需要使用Group by子句對數(shù)據(jù)進(jìn)行分組,還能在同一行返回原來行的列和使用聚合函數(shù)得到的聚合列。
那為什么叫窗口函數(shù)呢?因?yàn)榇翱诤瘮?shù)將表以窗口為單位進(jìn)行分割,并在其中進(jìn)行各種分析操作,為了讓大家快速形成直觀印象,才起了這樣一個(gè)容易理解的名稱。
SQL語法
如上代碼所示,窗口函數(shù)的語法分為四個(gè)部分:
函數(shù)子句:指明具體操作,如sum-求和,first_value-取第一個(gè)值;
partition by子句:指明分區(qū)字段,如果沒有,則將所有數(shù)據(jù)作為一個(gè)分區(qū);
order by子句:指明了每個(gè)分區(qū)排序的字段和方式,也是可選的,沒有就是按照表中的順序;
窗口子句:指明相對當(dāng)前記錄的計(jì)算范圍,可以向上(preceding),可以向下(following),也可以使用between指明,上下邊界的值,沒有的話默認(rèn)為當(dāng)前分區(qū)。有些場景比較特殊,后文會講到這種場景。
窗口函數(shù)分類
下面的思維導(dǎo)圖基本包含了Hive所有的窗口函數(shù),按照窗口函數(shù)的功能分為:計(jì)算、取值、排序、序列四種,前三種的使用場景比較常見,容易理解,最后一種(序列)的使用場景比較少。
窗口函數(shù)使用場景
介紹了這么多,那窗口函數(shù)到底可以幫我們做什么呢?
結(jié)合實(shí)際場景看看怎么用窗口函數(shù)來解決問題。下面針對不同的使用場景,將窗口函數(shù)的使用呈現(xiàn)給大家。所有例子的數(shù)據(jù)均來自下圖這張表。
用于輔助計(jì)算
主要的用法是在原有表的基礎(chǔ)上,增加一列聚合后的值,輔以后續(xù)的計(jì)算。
例如:統(tǒng)計(jì)出不同產(chǎn)品類型售價(jià)最高的產(chǎn)品。
具體代碼如下:
--使用窗口函數(shù)max select a.product_type,a.product_name from (select product_name,product_type,sale_price,max(sale_price) over (partition by product_type)?as?max_sale_price?--增加一列為聚合后的最高售價(jià)from product ) a where?a.sale_price?=?a.max_sale_price;? --保留與最高售價(jià)相同的記錄數(shù)執(zhí)行結(jié)果:
幾乎所有的窗口函數(shù)都可以用于輔助計(jì)算。
累積計(jì)算
標(biāo)準(zhǔn)聚合函數(shù)作為窗口函數(shù)配合order by使用,可以實(shí)現(xiàn)累積計(jì)算。
例如:sum窗口函數(shù)配合order by,可以實(shí)現(xiàn)累積和。
具體代碼如下:
SELECT product_id,product_name,product_type,sale_price,SUM(sale_price) OVER (ORDER BY product_id)?AS?current_sum FROM?product;執(zhí)行結(jié)果:
相應(yīng)的AVG窗口函數(shù)配合order by,可以實(shí)現(xiàn)累積平均,max可以實(shí)現(xiàn)累積最大值,min可以實(shí)現(xiàn)累積最小值,count則可以實(shí)現(xiàn)累積計(jì)數(shù)。注意,只有計(jì)算類的窗口函數(shù)可以實(shí)現(xiàn)累積計(jì)算。
這里提出一個(gè)問題,為什么增加了order by就可以實(shí)現(xiàn)累積計(jì)算呢?讀者可以停頓思考一下!
答案馬上揭曉:標(biāo)準(zhǔn)聚合函數(shù)作為窗口函數(shù)使用的時(shí)候,在指明order by的情況下,如果沒有Window子句,則Window子句默認(rèn)為:RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(上邊界不限制,下邊界到當(dāng)前行)。
移動計(jì)算
移動計(jì)算是在分區(qū)和排序的基礎(chǔ)上,對計(jì)算范圍進(jìn)一步做出限定。
例如:按照產(chǎn)品ID排序,將最近3條的銷售價(jià)格進(jìn)行匯總平均。
具體代碼如下:
SELECT?product_id,product_name,sale_price,AVG(sale_price) over?(?ORDER?BY?product_id?rows?2?preceding?)?AS?moving_avg FROM product;rows 2 preceding的意思就是“截止到之前2行”。也就是將作為匯總對象的記錄限定為如下的最靠近的3行。
執(zhí)行結(jié)果如下:
使用關(guān)鍵字FOLLOWING(“之后”)替換PRECEDING,就可以指定截止到之后~行。
取任一字段值
取值的窗口函數(shù)有:first_value/last_value、lag/lead,其中first_value和lag在開篇的例子中已經(jīng)使用到了,這里就不舉例說明了。只細(xì)化說明下他們的語法。
first_value(字段名)-取出分區(qū)中的第一條記錄的任意一個(gè)字段的值,可以排序也可以不排序,此處也可以進(jìn)一步指明Window子句。
lag(字段名,N,默認(rèn)值)-取出當(dāng)前行之上的第N條記錄的任意一個(gè)字段的值,這里的N和默認(rèn)值都是可選的,默認(rèn)N為1,默認(rèn)值為null。
排序
排序?qū)?yīng)的四個(gè)窗口函數(shù)為:rank、dense_rank、row_number、ntitle
rank:計(jì)算排序時(shí),如果存在相同位次的記錄,則會跳過之后的位次。
e.g. 有三條記錄排在第1位時(shí):1位、1位、1位、4位......
dense_rank:計(jì)算排序時(shí),即使存在相同位次的記錄,也不會跳過之后的位次。
e.g. 有三條記錄排在第1位時(shí):1位、1位、1位、2位......
row_number:賦予唯一的連續(xù)位次。
e.g. 有三條記錄排在第1位時(shí):1位、2位、3位、4位...
ntitle:用于將分組數(shù)據(jù)按照順序切分成n片,返回當(dāng)前切片值
e.g. 對于一組數(shù)字(1,2,3,4,5,6),ntile(2)切片后為(1,1,1,2,2,2)
1)統(tǒng)計(jì)所有產(chǎn)品的售價(jià)排名
具體代碼如下:
SELECT?product_name,product_type,sale_price,RANK?()?OVER?(ORDER?BY?sale_price?)?AS?ranking FROM product;執(zhí)行結(jié)果如下:
2)統(tǒng)計(jì)各產(chǎn)品類型下各產(chǎn)品的售價(jià)排名
具體代碼如下:
SELECT?product_name,product_type,sale_price,RANK?()?OVER?(PARTITION?BY?product_type?ORDER?BY?sale_price?)?AS?ranking FROM product;執(zhí)行結(jié)果如下:
對比一下dense_rank、row_number、ntile
具體代碼如下:
SELECT product_name,product_type,sale_price,RANK () OVER (ORDER BY sale_price) AS ranking,DENSE_RANK () OVER (ORDER BY sale_price) AS dense_ranking,ROW_NUMBER () OVER (ORDER BY sale_price) AS row_num,ntile(3) OVER (ORDER BY sale_price) as nt1,ntile(30) OVER (ORDER BY sale_price) as nt2 --切片大于總記錄數(shù) FROM?product;執(zhí)行結(jié)果如下:
從結(jié)果可以發(fā)現(xiàn),當(dāng)ntile(30)中的切片大于了總記錄數(shù)時(shí),切片的值為記錄的序號。
序列
序列中的兩個(gè)窗口函數(shù)cume_dist和percent_rank,通過實(shí)例來看看它們是怎么使用的。
1)統(tǒng)計(jì)小于等于當(dāng)前售價(jià)的產(chǎn)品數(shù),所占總產(chǎn)品數(shù)的比例
具體代碼如下:
SELECT product_type,product_name,sale_price, CUME_DIST() OVER(ORDER BY sale_price) AS rn1, CUME_DIST() OVER (PARTITION BY product_type ORDER BY sale_price )?AS?rn2? FROM?product;執(zhí)行結(jié)果如下:
rn1: 沒有partition,所有數(shù)據(jù)均為1組,總行數(shù)為8,
? ? ?第一行:小于等于100的行數(shù)為1,因此,1/8=0.125
? ? ?第二行:小于等于500的行數(shù)為3,因此,3/8=0.375
rn2: 按照產(chǎn)品類型分組,product_type=廚房用品的行數(shù)為4,
? ? ?第三行:小于等于500的行數(shù)為1,因此,1/4=0.25
2)統(tǒng)計(jì)每個(gè)產(chǎn)品的百分比排序
當(dāng)前行的RANK值-1/分組內(nèi)總行數(shù)-1
具體代碼如下:
SELECT product_type,product_name,sale_price, percent_rank() OVER (ORDER BY sale_price) AS rn1, percent_rank() OVER (PARTITION BY product_type ORDER BY sale_price )??AS?rn2? FROM?product;執(zhí)行結(jié)果如下:
rn1: 沒有partition,所有數(shù)據(jù)均為1組,總行數(shù)為8,
第一行:排序?yàn)?,因此,(1-1)/(8-1)= 0
第二行:排序?yàn)?,因此,(2-1)/(8-1)= 0.14
rn2: 按照產(chǎn)品類型分組,product_type=廚房用品的行數(shù)為4,
第三行:排序?yàn)?,因此,(1-1)/(4-1)= 0
第四行:排序?yàn)?,因此,(2-1)/(4-1)= 0.33
總結(jié)
以上介紹了Hive中窗口函數(shù)的幾乎所有的使用場景,每種函數(shù)的用法也配合代碼進(jìn)行講解,相信大家看了本文后,在實(shí)際數(shù)據(jù)工作中對于窗口函數(shù)的使用肯定會得心應(yīng)手。
總結(jié)
以上是生活随笔為你收集整理的Hive窗口函数进阶指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQLServer 不允许保存更改的解决
- 下一篇: excel解决线性规划求解问题