1. 程式人生 > >hive中的高階函式-視窗函式

hive中的高階函式-視窗函式

視窗函式的使用,配合聚合函式使用,能夠更加靈活的規約表的格式,大大減少工作量

說在前面

視窗函式,執行順序是最後執行僅僅是在order by之前執行。

over函式子句的使用

準備的測試資料

jackma,2018-01-01,10
tonyma,2018-01-02,15
jackma,2018-02-03,23
tonyma,2018-01-04,29
jackma,2018-01-05,46
jackma,2018-04-06,42
tonyma,2018-01-07,50
jackma,2018-01-08,55
martma,2018-04-08,62
martma,2018-04-09,68
neilma,2018-05-10,12
martma,2018-04-11,75
neilma,2018-06-12,80
martma,2018-04-13,94

create
table t_window(name string,orderdate string,cost double) row format delimited fields terminated by ',' ;

測試函式

檢視每個使用者購買的總次數

select name,count(*) as num from t_window group by name ;

select name,count(*)  over(partition by name ) as num from t_window ;

結果:
jackma 5
martma 4
neilma 2
tonyma 3

partition by子句

檢視每個使用者的消費,每個月的消費的全部使用者的消費額

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

jackma 1 10.0 205.0
jackma 1 55.0 205.0
tonyma 1 50.0 205.0
jackma 1 46.0 205.0
tonyma 1 29.0 205.0
tonyma 1 15.0 205.0
jackma 2 23.0 23.0
martma 4 94.0 341.0
jackma 4 42.0 341.0
martma 4 75.0 341.0
martma 4 68.0 341.0
martma 4 62.0 341.0
neilma 5 12.0 12.0
neilma 6 80.0 80.0

查詢結果 可以看到都是按照月 每個人的消費都是按照月進行彙總的,需要注意的是總的條數和原來的t_window表是完保持一致的。

order by 子句

在上面的例子中增加,要是需要累計日期每日的累計消費額 可以增加order by子句

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

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

jackma 2018-01-01 10.0 10.0
tonyma 2018-01-02 15.0 25.0
tonyma 2018-01-04 29.0 54.0
jackma 2018-01-05 46.0 100.0
tonyma 2018-01-07 50.0 150.0
jackma 2018-01-08 55.0 205.0
jackma 2018-02-03 23.0 23.0
jackma 2018-04-06 42.0 42.0
martma 2018-04-08 62.0 104.0
martma 2018-04-09 68.0 172.0
martma 2018-04-11 75.0 247.0
martma 2018-04-13 94.0 341.0
neilma 2018-05-10 12.0 12.0
neilma 2018-06-12 80.0 80.0

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

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

window子句

上面是使用了partition by 進行了分組,如果想進行更加細的劃分,可以使用window子句。

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

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

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分組,按照日期從小到大進行 cost的累加
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;

jackma 2018-01-01 10.0 661.0 176.0 10.0 10.0 10.0 56.0 176.0
jackma 2018-01-05 46.0 661.0 176.0 56.0 56.0 56.0 111.0 166.0
jackma 2018-01-08 55.0 661.0 176.0 111.0 111.0 101.0 124.0 120.0
jackma 2018-02-03 23.0 661.0 176.0 134.0 134.0 78.0 120.0 65.0
jackma 2018-04-06 42.0 661.0 176.0 176.0 176.0 65.0 65.0 42.0
martma 2018-04-08 62.0 661.0 299.0 62.0 62.0 62.0 130.0 299.0
martma 2018-04-09 68.0 661.0 299.0 130.0 130.0 130.0 205.0 237.0
martma 2018-04-11 75.0 661.0 299.0 205.0 205.0 143.0 237.0 169.0
martma 2018-04-13 94.0 661.0 299.0 299.0 299.0 169.0 169.0 94.0
neilma 2018-05-10 12.0 661.0 92.0 12.0 12.0 12.0 92.0 92.0
neilma 2018-06-12 80.0 661.0 92.0 92.0 92.0 92.0 92.0 80.0
tonyma 2018-01-02 15.0 661.0 94.0 15.0 15.0 15.0 44.0 94.0
tonyma 2018-01-04 29.0 661.0 94.0 44.0 44.0 44.0 94.0 79.0
tonyma 2018-01-07 50.0 661.0 94.0 94.0 94.0 79.0 79.0 50.0

序列函式

NTILE
  • NTILE(n),用於將分組資料按照順序切分成n片,返回當前切片值
  • NTILE不支援ROWS BETWEEN,

比如 NTILE(2) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)

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 ;

jackma 2018-01-01 10.0 3 1 1 1
jackma 2018-02-03 23.0 3 1 1 1
jackma 2018-04-06 42.0 2 2 2 2
jackma 2018-01-05 46.0 2 2 2 2
jackma 2018-01-08 55.0 2 3 2 3
martma 2018-04-08 62.0 2 1 2 1
martma 2018-04-09 68.0 1 2 3 1
martma 2018-04-11 75.0 1 3 3 2
martma 2018-04-13 94.0 1 1 3 3
neilma 2018-05-10 12.0 1 2 1 1
neilma 2018-06-12 80.0 1 1 3 2
tonyma 2018-01-02 15.0 3 2 1 1
tonyma 2018-01-04 29.0 3 3 1 2
tonyma 2018-01-07 50.0 2 1 2 3

最一列=1的就是消費最少的人的1/3 ,因為一共是有14條記錄,所以之後分佈不均的,分配大了第一部分。

row_number、rank、dense_rank

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

    select name,orderdate,cost,
    row_number() over(partition by name order by cost asc) ,
    rank() over(partition by name order by cost asc),
    dense_rank() over(partition by name order by cost asc)
    from t_window;

jackma 2018-01-01 10.0 1 1 1
jackma 2018-01-01 10.0 2 1 1
jackma 2018-02-03 23.0 3 3 2
jackma 2018-04-06 42.0 4 4 3
jackma 2018-01-05 46.0 5 5 4
jackma 2018-01-08 55.0 6 6 5
martma 2018-04-08 62.0 1 1 1
martma 2018-04-09 68.0 2 2 2
martma 2018-04-11 75.0 3 3 3
martma 2018-04-13 94.0 4 4 4
neilma 2018-05-10 12.0 1 1 1
neilma 2018-06-12 80.0 2 2 2
tonyma 2018-01-02 15.0 1 1 1
tonyma 2018-01-02 15.0 2 1 1
tonyma 2018-01-04 29.0 3 3 2
tonyma 2018-01-07 50.0 4 4 3

LAG和LEAD函式

可以檢視當前行的上一行或者下一行的記錄;

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

jackma 2018-01-01 10.0 1900-01-01 NULL
jackma 2018-01-01 10.0 2018-01-01 NULL
jackma 2018-01-05 46.0 2018-01-01 2018-01-01
jackma 2018-01-08 55.0 2018-01-05 2018-01-01
jackma 2018-02-03 23.0 2018-01-08 2018-01-05
jackma 2018-04-06 42.0 2018-02-03 2018-01-08
martma 2018-04-08 62.0 1900-01-01 NULL
martma 2018-04-09 68.0 2018-04-08 NULL
martma 2018-04-11 75.0 2018-04-09 2018-04-08
martma 2018-04-13 94.0 2018-04-11 2018-04-09
neilma 2018-05-10 12.0 1900-01-01 NULL
neilma 2018-06-12 80.0 2018-05-10 NULL
tonyma 2018-01-02 15.0 1900-01-01 NULL
tonyma 2018-01-02 15.0 2018-01-02 NULL
tonyma 2018-01-04 29.0 2018-01-02 2018-01-02
tonyma 2018-01-07 50.0 2018-01-04 2018-01-02

lag(orderdate,1,’1900-01-01’ ),引數的定義 orderdate 選定的欄位,往上查詢的行數,預設值(不給就是null)

lead函式就是向下取多少行,使用方式都是一致的。

first_value和last_value

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

select name,orderdate,cost ,
first_value(cost) over(partition by name order by cost),--取分組內的第一個值
first_value(cost) over (partition by name order by cost desc) --取分組內最後垂岸的一個值(一定要排序不能按照自然順序來去不然會報錯)
from t_window;

要是取分組內的第一個值和最後一個值(都是採用first value來實現):
jackma 2018-01-08 55.0 10.0 55.0
jackma 2018-01-05 46.0 10.0 55.0
jackma 2018-04-06 42.0 10.0 55.0
jackma 2018-02-03 23.0 10.0 55.0
jackma 2018-01-01 10.0 10.0 55.0
jackma 2018-01-01 10.0 10.0 55.0
martma 2018-04-13 94.0 62.0 94.0
martma 2018-04-11 75.0 62.0 94.0
martma 2018-04-09 68.0 62.0 94.0
martma 2018-04-08 62.0 62.0 94.0
neilma 2018-06-12 80.0 12.0 80.0
neilma 2018-05-10 12.0 12.0 80.0
tonyma 2018-01-07 50.0 15.0 50.0
tonyma 2018-01-04 29.0 15.0 50.0
tonyma 2018-01-02 15.0 15.0 50.0
tonyma 2018-01-02 15.0 15.0 50.0

在使用視窗函式中的分析函式,比如row_number,last_value函式d的時候一定要試用order by,不然最後的結果可能和你想要的結果有出入。