天天看點

Hive視窗函數Over和排序函數Rank

- 目錄

1、聚合函數+over

2、partition by子句

3、order by子句

4、★window子句(裡面包含) - PRECEDING:往前 - FOLLOWING:往後 - CURRENT ROW:目前行 - UNBOUNDED:起點

5、★視窗函數中的序列函數:包含NTILE(n),row_number、rank、dense_rank

簡介

本文主要介紹hive中的視窗函數.hive中的視窗函數和sql中的視窗函數相類似,都是用來做一些資料分析類的工作,一般用于olap分析

注意:OVER():指定分析函數工作的資料視窗大小,決定了聚合函數的範圍,這個資料視窗大小可能會随着行的變而變化,同時可以使用以下進行限定範圍。

概念

我們都知道在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           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在hive中建立一張表t_window,将資料插入進去.

執行個體

聚合函數+over

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

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

得到的結果如下:

name    count_window_0
mart    
mart    
mart    
mart    
jack               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

第一種:distinct

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

第二種:group by

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

執行後的結果如下:

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           
  • 1
  • 2

執行結果如下:

name    orderdate   cost    sum_window_0
jack    --    
jack    --    
tony    --    
jack    --    
tony    --    
tony    --    
jack    --    
mart    --    
jack    --    
mart    --    
mart    --    
mart    --    
neil    --    
neil    --               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以看出資料已經按照月進行彙總了.

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           
  • 1
  • 2

得到的結果如下:(order by預設情況下聚合從起始行當目前行的資料)

name    orderdate   cost    sum_window_0
jack    --    
tony    --    
tony    --    
jack    --    
tony    --    
jack    --    
jack    --    
jack    --    
mart    --    
mart    --    
mart    --    
mart    --    
neil    --    
neil    --               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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  PRECEDING   and current row) as sample5, --目前行和前面一行做聚合
sum(cost) over(partition by name order by orderdate rows between  PRECEDING   AND  FOLLOWING  ) as sample6,--目前行和前邊一行及後面一行
sum(cost) over(partition by name order by orderdate rows between current row and UNBOUNDED FOLLOWING ) as sample7 --目前行及後面所有行
from t_window;           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

得到查詢結果如下:

name    orderdate   cost    sample1 sample2 sample3 sample4 sample5 sample6 sample7
jack    --              
jack    --             
jack    --          
jack    --           
jack    --            
mart    --             
mart    --          
mart    --          
mart    --          
neil    --               
neil    --               
tony    --               
tony    --               
tony    --                          
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

視窗函數中的序列函數

主要序列函數是不支援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() over() as sample1 , --全局資料切片
       ntile() over(partition by name), -- 按照name進行分組,在分組内将資料切成3份
       ntile() over(order by cost),--全局按照cost升序排列,資料切成3份
       ntile() over(partition by name order by cost ) --按照name分組,在分組内按照cost升序排列,資料切成3份
from t_window           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

得到的資料如下:

name    orderdate   cost    sample1 sample2 sample3 sample4
jack    --             
jack    --             
jack    --             
jack    --             
jack    --             
mart    --             
mart    --             
mart    --             
mart    --             
neil    --             
neil    --             
tony    --             
tony    --             
tony    --                        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如上述資料,我們去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           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
rn1: 15号和16号并列第3, 13号排第5
rn2: 15号和16号并列第3, 13号排第4
rn3: 如果相等,則按記錄值排序,生成唯一的次序,如果所有記錄值都相等,或許會随機排吧。           
  • 1
  • 2
  • 3

LAG和LEAD函數

這兩個函數為常用的視窗函數,可以傳回上下資料行的資料.

以我們的訂單表為例,假如我們想要檢視顧客上次的購買時間可以這樣去查詢

select name,orderdate,cost,
lag(orderdate,,'1900-01-01') over(partition by name order by orderdate ) as time1,
lag(orderdate,) over (partition by name order by orderdate) as time2
from t_window;           
  • 1
  • 2
  • 3
  • 4

查詢後的資料為:

name    orderdate   cost    time1   time2
jack    --    --  NULL
jack    --    --  NULL
jack    --    --  --
jack    --    --  --
jack    --    --  --
mart    --    --  NULL
mart    --    --  NULL
mart    --    --  --
mart    --    --  --
neil    --    --  NULL
neil    --    --  NULL
tony    --    --  NULL
tony    --    --  NULL
tony    --    --  --           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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           
  • 1
  • 2
  • 3
  • 4

查詢結果如下:

name    orderdate   cost    time1   time2
jack    --    --  --
jack    --    --  --
jack    --    --  --
jack    --    --  --
jack    --    --  --
mart    --    --  --
mart    --    --  --
mart    --    --  --
mart    --    --  --
neil    --    --  --
neil    --    --  --
tony    --    --  --
tony    --    --  --
tony    --    --  --
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
轉載  https://blog.csdn.net/qq_26937525/article/details/54925827
           

繼續閱讀