背景:
平常我們使用 hive或者 mysql時,一般聚合函數用的比較多。但對於某些偏分析的需求,group by可能很費力,子查詢很多,這個時候就需要使用窗口分析函數了~
注:hive、oracle提供開窗函數,mysql8之前版本不提供,但Oracle發布的 MySQL 8.0版本支持窗口函數(over)和公用表表達式(with)這兩個重要的功能!
版本: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 取分組內排序后,截止到當前行,最后一個值
這兩個函數還是經常用到的(往往和排序配合使用),比較實用!
