Hive:窗口函數(轉載)

簡介

本文主要介紹hive中的窗口函數.hive中的窗口函數和sql中的窗口函數相類似,都是用來做一些數據分析類的工作,一般用於olap分析(在線分析處理)。

概念

我們都知道在sql中有一類函數叫做聚合函數,例如sum()、avg()、max()等等,這類函數可以將多行數據按照規則聚集爲一行,一般來講聚集後的行數是要少於聚集前的行數的.但是有時我們想要既顯示聚集前的數據,又要顯示聚集後的數據,這時我們便引入了窗口函數.

在深入研究Over字句之前,一定要注意:在SQL處理中,窗口函數都是最後一步執行,而且僅位於Order by字句之前。

數據準備

我們準備一張order表,字段分別爲name,orderdate,cost.數據內容如下:

jack,2015-01-01,10
tony,2015-01-02,15
jack,2015-02-03,23
tony,2015-01-04,29
jack,2015-01-05,46
jack,2015-04-06,42
tony,2015-01-07,50
jack,2015-01-08,55
mart,2015-04-08,62
mart,2015-04-09,68
neil,2015-05-10,12
mart,2015-04-11,75
neil,2015-06-12,80
mart,2015-04-13,94

在hive中建立一張表t_window,將數據插入進去.

實例

聚合函數+over

假如說我們想要查詢在2015年4月份購買過的顧客及總人數,我們便可以使用窗口函數去去實現

select name,count(*) over ()
from t_window
where substring(orderdate,1,7) = '2015-04'

得到的結果如下:

name    count_window_0
mart    5
mart    5
mart    5
mart    5
jack    5

可見其實在2015年4月一共有5次購買記錄,mart購買了4次,jack購買了1次.事實上,大多數情況下,我們是隻看去重後的結果的.針對於這種情況,我們有兩種實現方式

第一種:distinct

select distinct name,count(*) over ()
from t_window
where substring(orderdate,1,7) = '2015-04'

第二種:group by

select name,count(*) over ()
from t_window
where substring(orderdate,1,7) = '2015-04'
group by name

執行後的結果如下: 

name count_window_0 
mart 2 
jack 2

partition by子句

Over子句之後第一個提到的就是Partition By.Partition By子句也可以稱爲查詢分區子句,非常類似於Group By,都是將數據按照邊界值分組,而Over之前的函數在每一個分組之內進行,如果超出了分組,則函數會重新計算.

實例

我們想要去看顧客的購買明細及月購買總額,可以執行如下的sql

select name,orderdate,cost,sum(cost) over(partition by month(orderdate))
from t_window

執行結果如下:

name    orderdate   cost    sum_window_0
jack    2015-01-01  10  205
jack    2015-01-08  55  205
tony    2015-01-07  50  205
jack    2015-01-05  46  205
tony    2015-01-04  29  205
tony    2015-01-02  15  205
jack    2015-02-03  23  23
mart    2015-04-13  94  341
jack    2015-04-06  42  341
mart    2015-04-11  75  341
mart    2015-04-09  68  341
mart    2015-04-08  62  341
neil    2015-05-10  12  12
neil    2015-06-12  80  80

可以看出數據已經按照月進行彙總了.

order by子句

上述的場景,假如我們想要將cost按照月進行累加.這時我們引入order by子句.

order by子句會讓輸入的數據強制排序(文章前面提到過,窗口函數是SQL語句最後執行的函數,因此可以把SQL結果集想象成輸入數據)。Order By子句對於諸如Row_Number(),Lead(),LAG()等函數是必須的,因爲如果數據無序,這些函數的結果就沒有任何意義。因此如果有了Order By子句,則Count(),Min()等計算出來的結果就沒有任何意義。

我們在上面的代碼中加入order by

select name,orderdate,cost,sum(cost) over(partition by month(orderdate) order by orderdate )
from t_window

得到的結果如下:(order by默認情況下聚合從起始行到當前行的數據)

name    orderdate   cost    sum_window_0
jack    2015-01-01  10  10
tony    2015-01-02  15  25 //10+15
tony    2015-01-04  29  54 //10+15+29
jack    2015-01-05  46  100 //10+15+29+46
tony    2015-01-07  50  150
jack    2015-01-08  55  205
jack    2015-02-03  23  23
jack    2015-04-06  42  42
mart    2015-04-08  62  104
mart    2015-04-09  68  172
mart    2015-04-11  75  247
mart    2015-04-13  94  341
neil    2015-05-10  12  12
neil    2015-06-12  80  80
 

window子句

我們在上面已經通過使用partition by子句將數據進行了分組的處理.如果我們想要更細粒度的劃分,我們就要引入window子句了.

我們首先要理解兩個概念: 
- 如果只使用partition by子句,未指定order by的話,我們的聚合是分組內的聚合. 
- 使用了order by子句,未使用window子句的情況下,默認從起點到當前行.

當同一個select查詢中存在多個窗口函數時,他們相互之間是沒有影響的.每個窗口函數應用自己的規則.

window子句: 
- PRECEDING:往前 
- FOLLOWING:往後 
- CURRENT ROW:當前行 
- UNBOUNDED:起點,UNBOUNDED PRECEDING 表示從前面的起點, UNBOUNDED FOLLOWING:表示到後面的終點

我們按照name進行分區,按照購物時間進行排序,做cost的累加. 
如下我們結合使用window子句進行查詢

select name,orderdate,cost,
sum(cost) over() as sample1,--所有行相加
sum(cost) over(partition by name) as sample2,--按name分組,組內數據相加
sum(cost) over(partition by name order by orderdate) as sample3,--按name分組,組內數據累加
sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and current row )  as sample4 ,--和sample3一樣,由起點到當前行的聚合
sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING   and current row) as sample5, --當前行和前面一行做聚合
sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING   AND 1 FOLLOWING  ) as sample6,--當前行和前邊一行及後面一行
sum(cost) over(partition by name order by orderdate rows between current row and UNBOUNDED FOLLOWING ) as sample7 --當前行及後面所有行
from t_window;

得到查詢結果如下:

name

    orderdate   cost    sample1 sample2 sample3 sample4 sample5 sample6 sample7
jack    2015-01-01  10  661 176 10  10  10  56  176
jack    2015-01-05  46  661 176 56  56  56  111 166
jack    2015-01-08  55  661 176 111 111 101 124 120
jack    2015-02-03  23  661 176 134 134 78  120 65
jack    2015-04-06  42  661 176 176 176 65  65  42
mart    2015-04-08  62  661 299 62  62  62  130 299
mart    2015-04-09  68  661 299 130 130 130 205 237
mart    2015-04-11  75  661 299 205 205 143 237 169
mart    2015-04-13  94  661 299 299 299 169 169 94
neil    2015-05-10  12  661 92  12  12  12  92  92
neil    2015-06-12  80  661 92  92  92  92  92  80
tony    2015-01-02  15  661 94  15  15  15  44  94
tony    2015-01-04  29  661 94  44  44  44  94  79
tony    2015-01-07  50  661 94  94  94  79  79  50

窗口函數中的序列函數

主要序列函數是不支持window子句的.

hive中常用的序列函數有下面幾個:

NTILE

  • NTILE(n),用於將分組數據按照順序切分成n片,返回當前切片值

  • NTILE不支持ROWS BETWEEN, 
    比如 NTILE(2) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)

  • 如果切片不均勻,默認增加第一個切片的分佈

這個函數用什麼應用場景呢?假如我們想要每位顧客購買金額前1/3的交易記錄,我們便可以使用這個函數.

select name,orderdate,cost,
       ntile(3) over() as sample1 , --全局數據切片
       ntile(3) over(partition by name), -- 按照name進行分組,在分組內將數據切成3份
       ntile(3) over(order by cost),--全局按照cost升序排列,數據切成3份
       ntile(3) over(partition by name order by cost ) --按照name分組,在分組內按照cost升序排列,數據切成3份
from t_window

得到的數據如下:

name    orderdate   cost    sample1 sample2 sample3 sample4
jack    2015-01-01  10  3   1   1   1
jack    2015-02-03  23  3   1   1   1
jack    2015-04-06  42  2   2   2   2
jack    2015-01-05  46  2   2   2   2
jack    2015-01-08  55  2   3   2   3
mart    2015-04-08  62  2   1   2   1
mart    2015-04-09  68  1   2   3   1
mart    2015-04-11  75  1   3   3   2
mart    2015-04-13  94  1   1   3   3
neil    2015-05-10  12  1   2   1   1
neil    2015-06-12  80  1   1   3   2
tony    2015-01-02  15  3   2   1   1
tony    2015-01-04  29  3   3   1   2
tony    2015-01-07  50  2   1   2   3

如上述數據,我們去sample4 = 1的那部分數據就是我們要的結果

row_number

rank

dense_rank

這三個窗口函數的使用場景非常多 
- row_number()從1開始,按照順序,生成分組內記錄的序列,row_number()的值不會存在重複,當排序的值相同時,按照表中記錄的順序進行排列 
- RANK() 生成數據項在分組中的排名,排名相等會在名次中留下空位 
- DENSE_RANK() 生成數據項在分組中的排名,排名相等會在名次中不會留下空位

**注意: 
rank和dense_rank的區別在於排名相等時會不會留下空位.**

舉例如下:

SELECT 
cookieid,
createtime,
pv,
RANK() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rn1,
DENSE_RANK() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rn2,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY pv DESC) AS rn3 
FROM lxw1234 
WHERE cookieid = 'cookie1';
 
cookieid day           pv       rn1     rn2     rn3 
 
cookie1 2015-04-12      7       1       1       1
cookie1 2015-04-11      5       2       2       2
cookie1 2015-04-15      4       3       3       3
cookie1 2015-04-16      4       3       3       4
cookie1 2015-04-13      3       5       4       5
cookie1 2015-04-14      2       6       5       6
cookie1 2015-04-10      1       7       6       7
rn1: 15號和16號並列第3, 13號排第5
rn2: 15號和16號並列第3, 13號排第4
rn3: 如果相等,則按記錄值排序,生成唯一的次序,如果所有記錄值都相等,或許會隨機排吧。

LAG和LEAD函數

這兩個函數爲常用的窗口函數,可以返回上下數據行的數據. 
以我們的訂單表爲例,假如我們想要查看顧客上次的購買時間可以這樣去查詢

name    orderdate   cost    time1   time2
jack    2015-01-01  10  1900-01-01  NULL
jack    2015-01-05  46  2015-01-01  NULL
jack    2015-01-08  55  2015-01-05  2015-01-01
jack    2015-02-03  23  2015-01-08  2015-01-05
jack    2015-04-06  42  2015-02-03  2015-01-08
mart    2015-04-08  62  1900-01-01  NULL
mart    2015-04-09  68  2015-04-08  NULL
mart    2015-04-11  75  2015-04-09  2015-04-08
mart    2015-04-13  94  2015-04-11  2015-04-09
neil    2015-05-10  12  1900-01-01  NULL
neil    2015-06-12  80  2015-05-10  NULL
tony    2015-01-02  15  1900-01-01  NULL
tony    2015-01-04  29  2015-01-02  NULL
tony    2015-01-07  50  2015-01-04  2015-01-02

查詢後的數據爲:

  1. name orderdate cost time1 time2
  2. jack 2015-01-01 10 1900-01-01 NULL
  3. jack 2015-01-05 46 2015-01-01 NULL
  4. jack 2015-01-08 55 2015-01-05 2015-01-01
  5. jack 2015-02-03 23 2015-01-08 2015-01-05
  6. jack 2015-04-06 42 2015-02-03 2015-01-08
  7. mart 2015-04-08 62 1900-01-01 NULL
  8. mart 2015-04-09 68 2015-04-08 NULL
  9. mart 2015-04-11 75 2015-04-09 2015-04-08
  10. mart 2015-04-13 94 2015-04-11 2015-04-09
  11. neil 2015-05-10 12 1900-01-01 NULL
  12. neil 2015-06-12 80 2015-05-10 NULL
  13. tony 2015-01-02 15 1900-01-01 NULL
  14. tony 2015-01-04 29 2015-01-02 NULL
  15. tony 2015-01-07 50 2015-01-04 2015-01-02

time1取的爲按照name進行分組,分組內升序排列,取上一行數據的值,見下圖。

time2取的爲按照name進行分組,分組內升序排列,取上面2行的數據的值,注意當lag函數未設置行數值時,默認爲1行.設定取不到時的默認值時,取null值.

lead函數與lag函數方向相反,取向下的數據.

 

first_value和last_value

first_value取分組內排序後,截止到當前行,第一個值 
last_value取分組內排序後,截止到當前行,最後一個值

select name,orderdate,cost,
first_value(orderdate) over(partition by name order by orderdate) as time1,
last_value(orderdate) over(partition by name order by orderdate) as time2
from t_window

查詢結果如下:

name    orderdate   cost    time1   time2
jack    2015-01-01  10  2015-01-01  2015-01-01
jack    2015-01-05  46  2015-01-01  2015-01-05
jack    2015-01-08  55  2015-01-01  2015-01-08
jack    2015-02-03  23  2015-01-01  2015-02-03
jack    2015-04-06  42  2015-01-01  2015-04-06
mart    2015-04-08  62  2015-04-08  2015-04-08
mart    2015-04-09  68  2015-04-08  2015-04-09
mart    2015-04-11  75  2015-04-08  2015-04-11
mart    2015-04-13  94  2015-04-08  2015-04-13
neil    2015-05-10  12  2015-05-10  2015-05-10
neil    2015-06-12  80  2015-05-10  2015-06-12
tony    2015-01-02  15  2015-01-02  2015-01-02
tony    2015-01-04  29  2015-01-02  2015-01-04
tony    2015-01-07  50  2015-01-02  2015-01-07

原文參考:https://blog.csdn.net/qq_26937525/article/details/54925827 

 

擴展:

row_number的用途非常廣泛,排序最好用它,它會爲查詢出來的每一行記錄生成一個序號,依次排序且不會重複,注意使用row_number函數時必須要用over子句選擇對某一列進行排序才能生成序號。

rank函數用於返回結果集的分區內每行的排名,行的排名是相關行之前的排名數加一。簡單來說rank函數就是對查詢出來的記錄進行排名,與row_number函數不同的是,rank函數考慮到了over子句中排序字段值相同的情況,如果使用rank函數來生成序號,over子句中排序字段值相同的序號是一樣的,後面字段值不相同的序號將跳過相同的排名號排下一個,也就是相關行之前的排名數加一,可以理解爲根據當前的記錄數生成序號,後面的記錄依此類推。

dense_rank函數的功能與rank函數類似,dense_rank函數在生成序號時是連續的,而rank函數生成的序號有可能不連續。dense_rank函數出現相同排名時,將不跳過相同排名號,rank值緊接上一次的rank值。在各個分組內,rank()是跳躍排序,有兩個第一名時接下來就是第四名,dense_rank()是連續排序,有兩個第一名時仍然跟着第二名。

藉助實例能更直觀地理解:

假設現在有一張學生表student,學生表中有姓名、分數、課程編號。

select * from student;

 現在需要按照課程對學生的成績進行排序:

--row_number() 順序排序
select name,course,row_number() over(partition by course order by score desc) rank from student;

--rank() 跳躍排序,如果有兩個第一級別時,接下來是第三級別
select name,course,rank() over(partition by course order by score desc) rank from student;

dense_rank() 連續排序,如果有兩個第一級別時,接下來是第二級別 
select name,course,dense_rank() over(partition by course order by score desc) rank from student;

取得每門課程的第一名:

--每門課程第一名只取一個: 
select * from (select name,course,row_number() over(partition by course order by score desc) rank from student) where rank=1;
--每門課程第一名取所有: 
select * from (select name,course,dense_rank() over(partition by course order by score desc) rank from student) where rank=1;
--每門課程第一名取所有:
select * from (select name,course,rank() over(partition by course order by score desc) rank from student) where rank=1;
  附:每門課程第一名取所有的其他方法(使用group by 而不是partition by):

  附:每門課程第一名取所有的其他方法(使用group by 而不是partition by):

select s.* from student s
  inner join(select course,max(score) as score from student group by course) c
  on s.course=c.course and s.score=c.score; 
--或者使用using關鍵字簡化連接
select * from student s
  inner join(select course,max(score) as score from student group by course) c
  using(course,score);

關於Parttion by:

  Parttion by關鍵字是Oracle中分析性函數的一部分,用於給結果集進行分區。它和聚合函數Group by不同的地方在於它只是將原始數據進行名次排列,能夠返回一個分組中的多條記錄(記錄數不變),而Group by是對原始數據進行聚合統計,一般只有一條反映統計值的結果(每組返回一條)。

  TIPS:

  使用rank over()的時候,空值是最大的,如果排序字段爲null, 可能造成null字段排在最前面,影響排序結果。

  可以這樣: rank over(partition by course order by score desc nulls last)

總結:

  在使用排名函數的時候需要注意以下三點:

  1、排名函數必須有 OVER 子句。

  2、排名函數必須有包含 ORDER BY 的 OVER 子句。

  3、分組內從1開始排序。

參考:https://www.cnblogs.com/qiuting/p/7880500.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章