1.hive窗口函數語法
提到Hive SQL的窗口函數,很多開發者就想到row_number() over()
或者rank() over()
。甚至許多開發者包括之前本人也覺得row_number(),rank()就是最常用的窗口函數。其實這個理解是錯誤的。hive的窗口函數其實只有一個就是over()
,但是大多數情況下over()
不單獨使用,而是和分析函數組合使用,也就是說row_number()
和rank()
是分析函數。之所以有這樣的誤區是因為沒用弄清楚窗口函數的語法結構。下面就是HiveSQL的窗口函數的語法結構,其實其他支持SQL的數據庫的窗口函數語法結構和hive大體相同。
分析函數 over(partition by 列名 order by 列名 rows between 開始位置 and 結束位置)
說明:
在一般情況下,分析函數式不可缺的,rows between and
可以忽略,partition by
和order by
可以都有,可以只需要其中一個,這個具體看你查詢的業務而定。
與窗口函數over()
一起使用的分析函數有如下幾類:
a.聚合類
avg()、sum()、max()、min()
這類比較常用
b.排名類
row_number() --按照值排序時產生一個自增編號,不會重復(如:1、2、3、4、5、6)
rank() --按照值排序時產生一個自增編號,值相等時會重復,會產生空位(如:1、2、3、3、3、6)
dense_rank() --按照值排序時產生一個自增編號,值相等時會重復,不會產生空位(如:1、2、3、3、3、4)
這類是最常用的
c.其他類
lag --(列名,往前的行數,[行數為null時的默認值,不指定為null]),可以計算用戶上次購買時間,或者用戶下次購買時間。
lead --(列名,往后的行數,[行數為null時的默認值,不指定為null])
ntile(n) --把有序分區中的行分發到指定數據的組中,各個組有編號,編號從1開始,對於每一行,ntile返回此行所屬的組的編號
這類用得比較少,具體看業務查詢的要求
2.hive窗口函數實例1-模擬抖音用戶埋點數據
准備測試數據
如下是模擬抖音用戶埋點數據,當用戶上線,有一條埋點記錄,用戶下線也有一點埋點記錄
1,1,20201210,1607558400
2,2,20201210,1607558402
3,3,20201210,1607558403
4,4,20201210,1607558406
5,1,20201210,1607558500
6,2,20201210,1607558510
7,3,20201210,1607558520
8,5,20201210,1607558525
9,6,20201210,1607558529
10,4,20201210,1607558532
11,2,20201210,1607558535
12,5,20201210,1607558540
13,1,20201210,1607558545
14,6,20201210,1607558550
15,2,20201210,1607558560
16,1,20201210,1607558570
17,1,20201211,1607644805
18,2,20201211,1607644806
19,3,20201211,1607644809
20,4,20201211,1607644812
21,1,20201211,1607644815
22,2,20201211,1607644820
23,3,20201211,1607644828
24,5,20201211,1607644832
25,6,20201211,1607644843
26,4,20201211,1607644849
27,2,20201211,1607644856
28,5,20201211,1607644860
29,1,20201211,1607644863
30,6,20201211,1607644878
31,2,20201211,1607644885
32,1,20201211,1607644899
創建測試表並導入數據
CREATE EXTERNAL TABLE IF NOT EXISTS `douyin_maidian_log`
(
id string,
user_id string,
day string,
time_stamp int)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
load data local inpath '/home/test/maidian.log' into table douyin_maidian_log;
查詢用戶明細及當前表中數據總量
select *,count(user_id) over() as total from douyin_maidian_log;
查詢結果
可能會覺得不用窗口函數也可以實現,但是需要先查詢總數再與埋點明細表做關聯。
查詢用戶明細及當前每天的用戶埋點總數
select *,count(user_id) over(partition by day) from douyin_maidian_log;
查詢結果
查詢用戶明細及當前每天的用戶個數
select t1.*,t2.total from douyin_maidian_log t1,(select day,count(distinct user_id) total from douyin_maidian_log group by day) t2 where t1.day=t2.day;
查詢結果
這個查詢開窗函數查不出來
查詢每個用戶每次在線時的在線時長
埋點日志是一個user_id最早的一條記錄為上線時間,該user_id的下一條記錄為下線時間,緊挨着該條記錄的下一條記錄為再一次上線時間,依次類推
select t12.user_id,t11.time_stamp-t12.time_stamp,row_number() over(partition by t12.user_id) from
(select t1.user_id user_id,t1.time_stamp time_stamp,t1.rn rn from
(select user_id,time_stamp,row_number() over(partition by user_id order by time_stamp) as rn from douyin_maidian_log) t1 where t1.rn%2=1) t12,
(select t1.user_id user_id,t1.time_stamp time_stamp,t1.rn-1 rn from
(select user_id,time_stamp,row_number() over(partition by user_id order by time_stamp) as rn from douyin_maidian_log) t1 where t1.rn%2=0) t11
where t12.user_id=t11.user_id and t12.rn=t11.rn;
查詢結果
如上結果可以看出user_id為1的用戶有四次在線,有一次在線時長為25s,有一次在線時長為10s。關於這個查詢有優化空間或其他寫法,還有很多奧妙在,歡迎感興趣的讀者留言探討
引申出一個查詢:查詢當前埋點表每個用戶的總在線時長
select tb.user_id,max(tb.total) from
(select t12.user_id user_id,t11.time_stamp-t12.time_stamp total,row_number() over(partition by t12.user_id) rn from
(select t1.user_id user_id,t1.time_stamp time_stamp,t1.rn rn from
(select user_id,time_stamp,row_number() over(partition by user_id order by time_stamp) as rn from douyin_maidian_log) t1 where t1.rn%2=1) t12,
(select t1.user_id user_id,t1.time_stamp time_stamp,t1.rn-1 rn from
(select user_id,time_stamp,row_number() over(partition by user_id order by time_stamp) as rn from douyin_maidian_log) t1 where t1.rn%2=0) t11
where t12.user_id=t11.user_id and t12.rn=t11.rn) tb group by tb.user_id;
當然還可以引申出很多查詢:如當前每個用戶在線時長最大的一次是多長,總在線時長最多的user_id,在線次數最多的user_id,依照上面查詢,這些查詢都較為簡單不做舉例
可能有細心的讀者注意到,如果某個user_id的用戶只有一條上線記錄,或者以前user_id有上線或下線再次上線沒有下線時間,上面SQL是不是有問題,可以通過造數據驗證上面的SQL是沒有問題的。如果一直在線沒有下線的記錄,上線的這條記錄不會被統計在線時長上的
當然以上兩個查詢也可以查某一天當前所有用戶的在線時長,這里SQL就不舉例了,非常簡單。
3.hive窗口函數實例2
准備測試數據
20191020,11111,85
20191020,22222,83
20191020,33333,86
20191021,11111,87
20191021,22222,65
20191021,33333,98
20191022,11111,67
20191022,22222,34
20191022,33333,88
20191023,11111,99
20191023,22222,33
創建測試表並導入數據
CREATE EXTERNAL TABLE IF NOT EXISTS user_score
(day string,
userid string,
score int)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
load data local inpath '/home/test/user_score.log' into table user_score;
查詢每一天所有score大於80分的用戶總數
select t.day,max(t.total) from
(select day,userid,score,count(userid) over(partition by day rows between unbounded preceding and current row) total
from user_score
where score>80) t group by t.day;
查詢結果
這里這句rows between unbounded preceding and current row
加不加不影響查詢結果
查詢每個用戶到當前日期分數大於80的天數
select *,count(userid) over(partition by userid order by day) as total
from user_score where score>80 order by day,userid;
查詢結果
這里這句rows between unbounded preceding and current row
加不加不影響查詢結果
4.hive窗口函數實例3
准備測試數據
jack,2017-01-01,10
tony,2017-01-02,15
jack,2017-02-03,23
tony,2017-01-04,29
jack,2017-01-05,46
jack,2017-04-06,42
tony,2017-01-07,50
jack,2017-01-08,55
mart,2017-04-08,62
mart,2017-04-09,68
neil,2017-05-10,12
mart,2017-04-11,75
neil,2017-06-12,80
mart,2017-04-13,94
創建測試表並導入數據
create table business
(
name string,
orderdate string,
cost int
)ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
#加載數據
load data local inpath "/home/test/business.log" into table business;
查詢在2017年4月份購買過的顧客及總人數
select *,count(name) over() as total from business where substr(orderdate,1,7)='2017-04';
查詢結果
這里用group by查詢不出來的,比如用
select name,count(name) from business where substr(orderdate,1,7)='2017-04' group by name;
只能查詢出那些人購買及他購買的次數,如果想要得到以上結果就需要做子表關聯查詢
查詢每個顧客的購買明細及每個月購買總額
select *,sum(cost) over(partition by name,substr(orderdate,1,7)) total_amount from business;
查詢結果
查詢顧客的購買明細及到目前為止每個顧客購買總金額
select *,sum(cost) over(partition by name order by orderdate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) total_amount from business;
查詢結果
注意觀察查詢出的結果,結果是累加的,這種查詢在數據分析中比較常用,在實時數倉應用中也用得比較多
查詢顧客上次的購買時間,如果是第一次購買上次購買時間為null
select name,orderdate,cost,lag(orderdate,1) over(partition by name order by orderdate) last_date from business;
查詢結果
注意觀察查詢出的結果,這種查詢在數據分析中比較常用,在實時數倉應用中也用得比較多
5.hive窗口函數實例4
准備測試數據
孫悟空,語文,87
孫悟空,數學,95
孫悟空,英語,68
大海,語文,94
大海,數學,56
大海,英語,84
宋宋,語文,64
宋宋,數學,86
宋宋,英語,84
婷婷,語文,65
婷婷,數學,85
婷婷,英語,78
創表並加裝數據
create table score
(
name string,
subject string,
score int
) row format delimited fields terminated by ",";
#加載數據
load data local inpath '/home/test/score.log' into table score;
查詢每門學科學生成績排名
select *,
row_number() over(partition by subject order by score desc),--不並列排名
rank() over(partition by subject order by score desc),--並列空位排名
dense_rank() over(partition by subject order by score desc)--並列不空位
from score;
查詢結果
查詢每門學科成績排名top n的學生
select * from
(select *,row_number() over(partition by subject order by score desc) rmp from score
) t where t.rmp<=3;
查詢結果