Hive開窗函式總結
背景:
平常我們使用 hive或者 mysql時,一般聚合函式用的比較多。但對於某些偏分析的需求,group by可能很費力,子查詢很多,這個時候就需要使用視窗分析函數了~
注:hive、oracle提供開窗函式,mysql不提供
版本:Hive 1.1.0 + cdh5.13.0
一、介紹
分析函式用於計算基於組的某種聚合值,它和聚合函式的不同之處是:對於每個組返回多行,而聚合函式對於每個組只返回一行。
開窗函式指定了分析函式工作的資料視窗大小,這個資料視窗大小可能會隨著行的變化而變化!到底什麼是資料視窗?後面舉例會詳細講到!
1. 基礎結構:
分析函式(如:sum(),max(),row_number()... ) + 視窗子句(over函式)
2. over函式寫法:
over(partition by cookieid order by createtime)
先根據cookieid欄位分割槽,相同的cookieid分為一區,每個分割槽內根據createtime欄位排序(預設升序)
注:不加 partition by
的話則把整個資料集當作一個分割槽,不加 order by
的話會對某些函式統計結果產生影響,如sum()
3. 測試資料:
測試表test1
只有三個欄位 cookieid
、createtime
、pv
4. 視窗含義:
SELECT cookieid,createtime,pv,
SUM (pv) OVER(PARTITION BY cookieid ORDER BY createtime) AS pv1, -- 預設為從起點到當前行
SUM(pv) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS pv2, --從起點到當前行,結果同pv1
SUM(pv) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN 3 PRECEDING AND CURRENT ROW ) AS pv3, --當前行+往前3行
SUM(pv) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN 3 PRECEDING AND 1 FOLLOWING) AS pv4, --當前行+往前3行+往後1行
SUM(pv) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS pv5 ---當前行+往後所有行
FROM test1;
結果:
cookieid createtime pv pv1 pv2 pv3 pv4 pv5
a 2017-12-01 3 3 3 3 3 3
b 2017-12-00 3 3 3 3 3 3
cookie1 2017-12-10 1 1 1 1 6 26
cookie1 2017-12-11 5 6 6 6 13 25
cookie1 2017-12-12 7 13 13 13 16 20
cookie1 2017-12-13 3 16 16 16 18 13
cookie1 2017-12-14 2 18 18 17 21 10
cookie1 2017-12-15 4 22 22 16 20 8
cookie1 2017-12-16 4 26 26 13 13 4
cookie2 2017-12-12 7 7 7 7 13 14
cookie2 2017-12-16 6 13 13 13 14 7
cookie2 2017-12-24 1 14 14 14 14 1
cookie3 2017-12-22 5 5 5 5 5 5
注:這些視窗的劃分都是在分割槽內部!超過分割槽大小就無效了
相信大家看了後就會明白,如果不指定ROWS BETWEEN,預設統計視窗為從起點到當前行;如果不指定ORDER BY,則將分組內所有值累加;
關鍵是理解 ROWS BETWEEN
含義,也叫做window子句:
PRECEDING
:往前
FOLLOWING
:往後
CURRENT ROW
:當前行
UNBOUNDED
:無邊界,UNBOUNDED PRECEDING 表示從最前面的起點開始, UNBOUNDED FOLLOWING:表示到最後面的終點
–其他AVG,MIN,MAX,和SUM用法一樣
二、SUM 函式
select cookieid,createtime,pv,
sum(pv) over(PARTITION BY cookieid ORDER BY createtime) as pv1
FROM test1
首先 PARTITION BY cookieid
,根據cookieid分割槽,各分割槽之間預設根據字典順序排序,ORDER BY createtime
,指定的是分割槽內部的排序,預設為升序
我們可以清晰地看到,視窗函式和聚合函式的不同,sum()函式可以根據每一行的視窗返回各自行對應的值,有多少行記錄就有多少個sum值,而group by只能計算每一組的sum,每組只有一個值!
其中sum()計算的是分割槽內排序後一個個疊加的值,和order by有關!
如果不加 order by會咋樣:
select cookieid,createtime,pv,
sum(pv) over(PARTITION BY cookieid) as pv1
FROM test1
可以看到,如果沒有order by,不僅分割槽內沒有排序,sum()計算的pv也是整個分割槽的pv
注:max()函式無論有沒有order by 都是計算整個分割槽的最大值
三、NTILE 函式
NTILE(n),用於將分組資料按照順序切分成n片,返回當前切片值
注1:如果切片不均勻,預設增加第一個切片的分佈
注2:NTILE不支援ROWS BETWEEN
SELECT cookieid,createtime,pv,
NTILE(2) OVER(PARTITION BY cookieid ORDER BY createtime) AS ntile1, --分組內將資料分成2片
NTILE(3) OVER(PARTITION BY cookieid ORDER BY createtime) AS ntile2, --分組內將資料分成3片
NTILE(4) OVER(PARTITION BY cookieid ORDER BY createtime) AS ntile3 --將所有資料分成4片
FROM test1
用法舉例:
統計一個cookie,pv數最多的前1/3的天:
SELECT cookieid,createtime,pv,
NTILE(3) OVER(PARTITION BY cookieid ORDER BY pv DESC) AS ntile
FROM test1;
取 ntile = 1
的記錄,就是我們想要的結果!
四、ROW_NUMBER 函式
ROW_NUMBER() 從1開始,按照順序,生成分組內記錄的序列
ROW_NUMBER() 的應用場景非常多,比如獲取分組內排序第一的記錄、獲取一個session中的第一條refer等。
SELECT cookieid,createtime,pv,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rn
FROM test1;
五、RANK 和 DENSE_RANK 函式
RANK()
生成資料項在分組中的排名,排名相等會在名次中留下空位
DENSE_RANK()
生成資料項在分組中的排名,排名相等會在名次中不會留下空位
我們把 rank、dense_rank、row_number三者對比,這樣比較清晰:
SELECT cookieid,createtime,pv,
RANK() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rank1,
DENSE_RANK() OVER(PARTITION BY cookieid ORDER BY pv desc) AS d_rank2,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY pv DESC) AS rn3
FROM test1
六、CUME_DIST 函式
cume_dist
返回小於等於當前值的行數/
分組內總行數
比如,我們可以統計小於等於當前薪水的人數,所佔總人數的比例
SELECT cookieid,createtime,pv,
round(CUME_DIST() OVER(ORDER BY pv),2) AS cd1,
round(CUME_DIST() OVER(PARTITION BY cookieid ORDER BY pv),2) AS cd2
FROM test1;
注:cd1沒有partition,所有資料均為1組!
七、PERCENT_RANK 函式
percent_rank
分組內當前行的RANK值-1/分組內總行數-1
注:一般不會用到該函式,可能在一些特殊演算法的實現中可以用到吧
SELECT cookieid,createtime,pv,
PERCENT_RANK() OVER(ORDER BY pv) AS rn1
from test1
八、LAG 和 LEAD 函式
LAG(col,n,DEFAULT)
用於統計視窗內往上第n行值
第一個引數為列名,第二個引數為往上第n行(可選,預設為1),第三個引數為預設值(當往上第n行為NULL時候,取預設值,如不指定,則為NULL)
SELECT cookieid,createtime,pv,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY createtime) AS rn,
LAG(createtime,1,'1970-01-01') OVER(PARTITION BY cookieid ORDER BY createtime) AS lag1,
LAG(createtime,2) OVER(PARTITION BY cookieid ORDER BY createtime) AS lag2
FROM test1;
LEAD 函式則與 LAG 相反:
LEAD(col,n,DEFAULT)
用於統計視窗內往下第n行值
第一個引數為列名,第二個引數為往下第n行(可選,預設為1),第三個引數為預設值(當往下第n行為NULL時候,取預設值,如不指定,則為NULL)
九、FIRST_VALUE 和 LAST_VALUE 函式
FIRST_VALUE
取分組內排序後,截止到當前行,第一個值
SELECT cookieid,createtime,pv,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY createtime) AS rn,
FIRST_VALUE(pv) OVER(PARTITION BY cookieid ORDER BY createtime) AS first
FROM test1;
LAST_VALUE 函式則相反:
LAST_VALUE
取分組內排序後,截止到當前行,最後一個值
這兩個函式還是經常用到的(往往和排序配合使用),比較實用!