前言:我們在學習hive窗口函數的時候,一定要先了解窗口函數的結構。而不是直接百度sum() over()、row_number() over()、或者count() over()的用法,如果這樣做,永遠也掌握不到窗口函數的核心,當然我剛開始的時候也是這樣做的。
還好我比較頑強,在HIVE窗口函數問題上折騰了半個月、看了很多文章后才知道over()才是窗口函數,而sum、row_number、count只是與over()搭配的分析函數,當然除了這三個函數還有其他的函數。
個人對over()的窗口理解:這個永遠是一行對應一個窗口,至於這個窗口的范圍是什么就要看over()函數里面對窗口范圍的約束是什么了(partition by order by between ... and)通過partition by 關鍵字來對窗口分組,特殊注意:通過order by 來對order by字段排序后的行進行開窗,只不過注意的是第一行數據的窗口大小是1,第二行數據的窗口范圍是前2行,第n行的窗口范圍是前n行,以此類推。如果里面沒有條件,則每一行對應整張表。
特殊的窗口函數如rank(),rownumber(),dense()等,即使后面over()里面沒有條件,默認的開窗類似order by效果,即第一行窗口大小為1,第二行窗口大小為2,以此類推,但是數據只不過沒有什統計意義,所以一般還是會在over()里加入partiton by和order by(分組,排序)等,為其賦予意義 如排名等。
over(partition by ) 和 普通的group by的區別,為什么不同group by,因為有group by,只能select group by 后面的字段,和一些聚合函數 sum(),avg(),max(),min()等,而用了over(partition by),還能select 別的非partition by 字段 或者能直接“select *”,而且對於join 等有更好的支持。
一、hive窗口函數語法
在前言中我們已經說了avg()、sum()、max()、min()是分析函數,而over()才是窗口函數,下面我們來看看over()窗口函數的語法結構、及常與over()一起使用的分析函數
- over()窗口函數的語法結構
- 常與over()一起使用的分析函數
- 窗口函數總結
1、over()窗口函數的語法結構
分析函數 over(partition by 列名 order by 列名 rows between 開始位置 and 結束位置)
over()函數中包括三個函數:包括分區partition by 列名、排序order by 列名、指定窗口范圍rows between 開始位置 and 結束位置。我們在使用over()窗口函數時,over()函數中的這三個函數可組合使用也可以不使用。
over()函數中如果不使用這三個函數,窗口大小是針對查詢產生的所有數據,如果指定了分區,窗口大小是針對每個分區的數據。
1.over() 默認此時每一行的窗口都是所有的行
select *,count(1) over() from business;

2.over(order by orderdate)
orderdate=1的窗口只有一行,orderdate=2的窗口包括orderdate=2017-01-01,orderdate=2017-01-02
select *,count(1) over(order by orderdate) from business;

3.over(partition by name)每一行根據 name來區分窗口
select *,sum(cost) over(partition by name) from business;

4.over(partition by name order by id) 每一行根據 name來區分窗口,再根據order by 取具體的范圍
select *,sum(cost) over(partition by name order by orderdate) from business;

1.1、over()函數中的三個函數講解
order by
order by是排序的意思,是該窗口中的
A、partition bypartition by可理解為group by 分組。over(partition by 列名)搭配分析函數時,分析函數按照每一組每一組的數據進行計算的。
B、rows between 開始位置 and 結束位置
是指定窗口范圍,比如第一行到當前行。而這個范圍是隨着數據變化的。over(rows between 開始位置 and 結束位置)搭配分析函數時,分析函數按照這個范圍進行計算的。
窗口范圍說明:
我們常使用的窗口范圍是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(表示從起點到當前行),常用該窗口來計算累加。
PRECEDING:往前
FOLLOWING:往后
CURRENT ROW:當前行
UNBOUNDED:起點(一般結合PRECEDING,FOLLOWING使用)
UNBOUNDED PRECEDING 表示該窗口最前面的行(起點)
UNBOUNDED FOLLOWING:表示該窗口最后面的行(終點)
比如說: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(表示從起點到當前行) ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING(表示往前2行到往后1行) ROWS BETWEEN 2 PRECEDING AND 1 CURRENT ROW(表示往前2行到當前行) ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING(表示當前行到終點)
2、常與over()一起使用的分析函數:
2.1 聚合類
avg()、sum()、max()、min()
2.2 排名類
row_number()按照值排序時產生一個自增編號,不會重復(如:1、2、3、4、5、6)
rank() 按照值排序時產生一個自增編號,值相等時會重復,會產生空位(如:1、2、3、3、3、6)
dense_rank() 按照值排序時產生一個自增編號,值相等時會重復,不會產生空位(如:1、2、3、3、3、4)
2.3 其他類
lag(列名,往前的行數,[行數為null時的默認值,不指定為null]),可以計算用戶上次購買時間,或者用戶下次購買時間。或者上次登錄時間和下次登錄時間
lead(列名,往后的行數,[行數為null時的默認值,不指定為null])
ntile(n) 把有序分區中的行分發到指定數據的組中,各個組有編號,編號從1開始,對於每一行,ntile返回此行所屬的組的編號
3、窗口函數總結:
其實窗口函數邏輯比較繞,我們可以把窗口理解為對表中的數據進行分組,排序等計算。要真正的理解HIVE窗口函數,還是要結合練習題才行。下面我們開始HIVE窗口函數的練習吧!
二、hive窗口函數練習28道題
第一套練習:hive之簡單窗口函數 over()
1、使用 over() 函數進行數據統計, 統計每個用戶及表中數據的總數
-
求用戶明細並統計每天的用戶總數
-
計算從第一天到現在的所有 score 大於80分的用戶總數
-
計算每個用戶到當前日期分數大於80的天數
測試數據
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 table test_window (logday string, #logday時間
userid string, score int) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','; #加載數據
load data local inpath '/home/xiaowangzi/hive_test_data/test_window.txt' into table test_window;
我們先看下表中的數據:
select * from test_window;

test_window
1、使用 over() 函數進行數據統計, 統計每個用戶及表中數據的總數
select *, count(userid) over() as total from test_window;

這里使用 over() 與 select count(*) 有相同的作用,好處就是,在需要計算總數時不用再進行一次關聯。
2、求用戶明細並統計每天的用戶總數
可以使用 partition by 按日期列對數據進行分區處理,如:over(partition by logday)
select *,count()over(partition by logday)as day_total from test_window;

求每天的用戶數可以使用select logday, count(userid) from recommend.test_window group by logday,但是當想要得到 userid 信息時,這種用法的優勢就很明顯。
3、計算從第一天到現在的所有 score 大於80分的用戶總數
此時簡單的分區不能滿足需求,需要將 order by 和 窗口定義結合使用。
select *,count()over(order by logday rows between unbounded preceding and current row)as total from test_window where score > 80;

通過 over() 計算出按日期的累加值。
其實自己剛開始的時候就計算我思路是錯了,我就想的是不用累加,直接select *,count()over()as total from test_window where score > 80;這樣計算,如果這樣計算的話只會顯示表中所有大於80的人數,如果我想看20191021或者看20191022的人數看不見。
4、計算每個用戶到當前日期分數大於80的天數
select *, count()over(partition by userid order by logday rows between unbounded preceding and current row) as total from test_window where score > 80 order by logday, userid;

第二套練習
有以下數據:字段名為:name、orderdate、cost
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
Mark,2017-04-08,62
Mart,2017-04-09,68
Meil,2017-05-10,12
Mart,2017-04-11,75
Meil,2017-06-12,80
Mart,2017-04-13,94
需 求
1、查詢2017-04購買的顧客總人數
2、顧客購買明細及月份總額
3、上述場景,將cost按日期累加
4、查詢顧客上次購買時間
5、查詢前20%購買的訂單信息
一、建表並導入數據:
-- 建表 create table business( name string, orderdate string, cost int) row format delimited fields terminated by ","; --導入數據 load data local inpath "/business.txt" into table business; -- 查詢表: select * from business;
需求分析
一、查詢2017-04購買的顧客總人數
a、首先想到使用聚合函數count()
-- 先求出2017-04這月一共有多少條記錄 select count(*) from business where substr(orderdate,1,7) = "2017-04";
結果如下圖:
b、現在按照顧客進行分組
select name,count(*) from business where substr(orderdate,1,7) = "2017-04" group by name;
結果如下圖:
數據被分成了三組:
使用over()函數:over只對聚合函數起作用,count分別對上面三個組內進行計數,over統計一共有多少個組(有一個count進行累加一次)
select name,count(*) over() total_num from business where substr(orderdate,1,7) = "2017-04" group by name;
結果如下所示:
二、查詢顧客購買明細及月份總額
a、首先選出所有明細信息:
select * from business;
b、求總額:(這是所有數據的總和,因為沒有分組(group by),所以over()的針對的是每一條數據)
select *, sum(cost) over() from business;
c、針對四月份的數據,我們需要進行求總額,
思路:分區或者分組,但是使用group by date,只能查詢date,(select date ,name group by date)其它字段不能查詢
解決:使用窗口函數,並對窗口函數進行分區over(distribute by()) 或者over(partition by())
select *,sum(cost) over(distribute by month(orderdate)) from business;
結果如圖所示:
三、上述場景,將cost按時間累加
a、先按照購買時間進行排序
select * from business sort by orderdate;
結果如圖所示:
-- 參數講解
-- sort by orderdate:按照購買日期進行排序
-- UNBOUNDED PRECEDING:從起點開始
-- CURRENT ROW:到當前行
-- 計算從開始到當前時間的總花費
select *, sum(cost) over(sort by orderdate rows between UNBOUNDED PRECEDING and CURRENT ROW) from business;
結果如下圖所示:
row函數:
current row:當前行
n PRECEDING:往前n行
n FOLLOWING:往后n行
UNBOUNDED:起點
UNBOUNDED PRECEDING:從前面起點
UNBOUNDED FOLLOWING:到后面終點
LAG(col,n):往前的第n行
LEAD(col,n):往后的第n行
--參數講解
-- sort by orderdate:按照時間排序
-- 1 preceding:當前行的前1行
-- 1 following:當前行的后一行
-- 計算相鄰三行的值(第一行計算當前行 + 后一行; 最后一行計算當前行 + 前一行)
select *, sum(cost) over(sort by orderdate rows between 1 preceding and 1 following) from business;
結果如下如所示:
demo2:
-- 參數詳解:
-- distribute by name:按名字進行分區
-- sort by orderdate:在每個分區中按照時間進行排序
-- UNBOUNDED PRECEDING and current row:從起點行到當前行
-- 計算每個人一共的總花費
select *, sum(cost) over(distribute by name sort by orderdate rows between UNBOUNDED PRECEDING and current row) from business;
結果如下圖所示:
demo3:
--參數講解:
-- sort by orderdate:按照時間排序
-- current row and unbounded following:當前行到終點行
select *, sum(cost) over(sort by orderdate rows between current row and unbounded following) from business;
結果如下圖所示:
四、查詢顧客上次購買時間,以及下次購買時間(電商網站常用於求頁面跳轉的前后時間)
分析:lag(clo,n):返回的是當前行的第前n行
-- 參數詳解:
-- distribute by name:按照姓名分組
-- sort by orderdate:按照時間排序
-- lag(orderdate,1):返回當前orderdate行的前一行
-- lead(orderdate,1):返回當前orderdate行的后一行
select *, lag(orderdate,1) over(distribute by name sort by orderdate), lead(orderdate,1) over(distribute by name sort by orderdate) from business;
結果如下圖所示:
五、查詢前20%購買的訂單信息
分析:可以按照時間分成五等份,然后返回其中的第一份
NTILE(n):將數據等分成n份
select *, ntile(5) over(sort by orderdate) from business;
結果如下圖所示:
-- 下面語句報錯,因為 ntile、sum、agg等函數不能放在where后面當做查詢條件
select *, ntile(5) over(sort by orderdate) as sorted from business where sorted = 1;
-- 下面語句報錯,因為having必須跟在group by 語句后面 select *, ntile(5) over(sort by orderdate) as sorted from business having sorted = 1;
-- 所以使用了子查詢,將上一步查詢的結果放在子句中 select name,orderdate,cost from ( select *,ntile(5) over(order by orderdate) sorted from business ) t where sorted = 1; -- Tips:子查詢不能使用select *
第三套練習
-
每門學科學生成績排名(是否並列排名、空位排名三種實現)
-
每門學科成績排名top n的學生
原始數據(學生成績信息)
name subject score 孫悟空 語文 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 "\t"; #加載數據 load data local inpath '/home/fengGG/hive_test_data/score.txt' into table score;
查看數據
select * from score;

1、每門學科學生成績排名(是否並列排名、空位排名三種實現)
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;

2、每門學科成績排名top n的學生
select * from( select *,row_number() over(partition by subject order by score desc) rmp from score ) t where t.rmp<=3;

