Hive 之 函數 02-常用查詢函數(二)
六、 窗口函數
6.1 函數說明
OVER()
: 指定分析函數工作的數據窗口大小,這個數據窗口大小可能會隨着行的變化而變化; 如果括號內爲空, 表示對整個數據集開窗;
over()
括號內可以寫的參數:
CURRENT ROW
: 當前行;
n PRECEDING
: 往前 n 行數據;
n FOLLOWING
: 往後 n 行數據;
UNBOUNDED
: 起點, UNBOUNDED PRECEDING
表示從前面的起點, UNBOUNDED FOLLOWING
表示到後面的終點;(如果想要計算某個時間段內整體的購買額, 使用 distribute by
加 sort by
又只能“累加”, 此時就可使用“起點”到“終點”這個)
over()
前面可以寫的參數:
LAG(col,n)
: 往前第 n 行數據; 可以接受第三個參數, 當爲 Null 時充當默認值;
LEAD(col,n)
: 往後第 n 行數據; 可以接受第三個參數, 當爲 Null 時充當默認值;
NTILE(n)
: 把有序分區中的行分發到指定數據的組中,各個組有編號,編號從 1 開始, 對於每一行, NTILE 返回此行所屬的組的編號。 注意: n 必須爲 int 類型。 主要用於求百分率內數據信息的情況。
【注】over()
放在聚合函數的後面, 它的作用是開窗, 範圍僅僅是針對它前面的聚合函數有效;
6.2 需求
○ 查詢在 2017 年 4 月份購買過的顧客及總人數
○ 查詢顧客的購買明細及購買總額
○ 上述的場景, 要將 cost 按照日期進行逐個累加
○ 查詢顧客上次的購買時間
○ 查詢前 20% 時間的訂單信息
6.3 實現
建表及導入數據:
hive (default)> create table business(
> name string,
> orderdate string,
> cost int)
> row format delimited fields terminated by ',';
OK
Time taken: 0.884 seconds
hive (default)> load data local inpath
> '/opt/module/data/business.txt'
> into table business;
Loading data to table default.business
Table default.business stats: [numFiles=1, totalSize=171]
OK
Time taken: 0.419 seconds
查詢全部數據:
hive (default)> select * from business;
OK
business.name business.orderdate business.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
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
Time taken: 2.0 seconds, Fetched: 14 row(s)
6.3.1 查詢在 2017 年 4 月份購買過的顧客及總人數
hive(default)> select name, count(1) over()
> from business
> where substring(orderdate, 1, 7) = '2017-04'
> group by name;
... ...
OK
name count_window_0
mart 2
jack 2
Time taken: 114.428 seconds, Fetched: 2 row(s)
【注】over()
是開窗函數, 針對【每一條】數據進行開窗, 如果括號內沒內容, 就會將所有符合 where 子句及後續限定語句的數據作爲開窗的數據給前面的聚合函數進行聚合計算; 本例中, mart 購買過 4 次, jack 購買過 1 次, 符合 where 子句的共有 5 條數據, 加上 group by
的限制, 就變成了兩條(因爲相同的 name 就只輸出了一條數據, 總共兩條), 所以第二列的 count 函數就會輸出 2。
如果本例中去掉 group by
的語句, 那麼就會將符合 where 子句的所有數據進行開窗(5 條), 那麼結果的第一列會出現一個 jack 和四個 mart, 而第二列就應該都是 5。
hive (default)> select name, count(*) over()
> from business
> where substring(orderdate, 1, 7) = '2017-04';
... ...
OK
name count_window_0
mart 5
mart 5
mart 5
mart 5
jack 5
Time taken: 23.664 seconds, Fetched: 5 row(s)
如果不加開窗函數 over()
, 那麼結果將是 2017 年 4 月份購買過的人, 及購買過的次數:
hive (default)> select name, count(*)
> from business
> where substring(orderdate, 1, 7) = '2017-04'
> group by name;
... ...
OK
name _c1
jack 1
mart 4
Time taken: 19.944 seconds, Fetched: 2 row(s)
6.3.2 查詢顧客的購買明細及購買總額
hive (default)> select name, orderdate, cost, sum(cost) over()
> from business;
... ...
OK
name orderdate cost sum_window_0
mart 2017-04-13 94 661
neil 2017-06-12 80 661
mart 2017-04-11 75 661
neil 2017-05-10 12 661
mart 2017-04-09 68 661
mart 2017-04-08 62 661
jack 2017-01-08 55 661
tony 2017-01-07 50 661
jack 2017-04-06 42 661
jack 2017-01-05 46 661
tony 2017-01-04 29 661
jack 2017-02-03 23 661
tony 2017-01-02 15 661
jack 2017-01-01 10 661
Time taken: 18.86 seconds, Fetched: 14 row(s)
6.3.3 上述的場景, 要將 cost 按照日期進行逐個累加
hive (default)> select orderdate, cost, sum(cost) over(order by orderdate)
> from business;
... ...
OK
orderdate cost sum_window_0
2017-01-01 10 10
2017-01-02 15 25
2017-01-04 29 54
2017-01-05 46 100
2017-01-07 50 150
2017-01-08 55 205
2017-02-03 23 228
2017-04-06 42 270
2017-04-08 62 332
2017-04-09 68 400
2017-04-11 75 475
2017-04-13 94 569
2017-05-10 12 581
2017-06-12 80 661
Time taken: 22.812 seconds, Fetched: 14 row(s)
【注】在 over()
函數中進行 order by
的時候, 第一次的數據只有日期最小的, 結果就爲 10, 第二次, 包含了最小和倒數第二小的日期的數據, 所以結果就是 10+15=25, 以此類推。
假如我現在想得到每個人的明細, 及每個人各自的總消費額, 則有:
hive (default)> select name, orderdate, cost, sum(cost) over(distribute by name)
> from business;
... ...
OK
name orderdate cost sum_window_0
jack 2017-01-05 46 176
jack 2017-01-08 55 176
jack 2017-01-01 10 176
jack 2017-04-06 42 176
jack 2017-02-03 23 176
mart 2017-04-13 94 299
mart 2017-04-11 75 299
mart 2017-04-09 68 299
mart 2017-04-08 62 299
neil 2017-05-10 12 92
neil 2017-06-12 80 92
tony 2017-01-04 29 94
tony 2017-01-02 15 94
tony 2017-01-07 50 94
Time taken: 19.979 seconds, Fetched: 14 row(s)
【注】窗口函數中的條件是按照 name 進行分區, 那麼結果就是計算每個分區後單獨分區的 cost 總和了。 不能在 over()
函數中使用 group by
, 會報錯!
假如現在想要每個人的購買明細, 並且按照購買日期排序, 同時還要把每個人的購買額逐一累加結果展示出來:
hive (default)> select name, orderdate, cost, sum(cost) over(distribute by name sort by orderdate)
> from business;
... ...
OK
name orderdate cost sum_window_0
jack 2017-01-01 10 10
jack 2017-01-05 46 56
jack 2017-01-08 55 111
jack 2017-02-03 23 134
jack 2017-04-06 42 176
mart 2017-04-08 62 62
mart 2017-04-09 68 130
mart 2017-04-11 75 205
mart 2017-04-13 94 299
neil 2017-05-10 12 12
neil 2017-06-12 80 92
tony 2017-01-02 15 15
tony 2017-01-04 29 44
tony 2017-01-07 50 94
Time taken: 30.149 seconds, Fetched: 14 row(s)
6.3.4 查詢顧客上次的購買時間
hive (default)> select name, orderdate, cost,
> lag(orderdate, 1) over(distribute by name sort by orderdate)
> from business;
... ...
OK
name orderdate cost lag_window_0
jack 2017-01-01 10 NULL
jack 2017-01-05 46 2017-01-01
jack 2017-01-08 55 2017-01-05
jack 2017-02-03 23 2017-01-08
jack 2017-04-06 42 2017-02-03
mart 2017-04-08 62 NULL
mart 2017-04-09 68 2017-04-08
mart 2017-04-11 75 2017-04-09
mart 2017-04-13 94 2017-04-11
neil 2017-05-10 12 NULL
neil 2017-06-12 80 2017-05-10
tony 2017-01-02 15 NULL
tony 2017-01-04 29 2017-01-02
tony 2017-01-07 50 2017-01-04
Time taken: 14.933 seconds, Fetched: 14 row(s)
【注】要想不出現 Null, 可以在 lag 函數中傳第三個參數, 可作爲一個默認值, 如果遇到 Null, 則會以默認值替代;
如果需求變爲 “查詢顧客下次的購買時間” 就將 lag 函數換成 lead 即可;
6.3.5 查詢前 20% 時間的訂單信息
首先, 將數據分成五 “組”, 使用 ntile()
函數即可:
hive (default)> select name, orderdate, cost,
> ntile(5) over(order by orderdate)
> from business;
... ...
OK
name orderdate cost ntile_window_0
jack 2017-01-01 10 1
tony 2017-01-02 15 1
tony 2017-01-04 29 1
jack 2017-01-05 46 2
tony 2017-01-07 50 2
jack 2017-01-08 55 2
jack 2017-02-03 23 3
jack 2017-04-06 42 3
mart 2017-04-08 62 3
mart 2017-04-09 68 4
mart 2017-04-11 75 4
mart 2017-04-13 94 4
neil 2017-05-10 12 5
neil 2017-06-12 80 5
Time taken: 54.086 seconds, Fetched: 14 row(s)
從五組裏面取出第一組的數據, 即是 “前 20%” 時間的數據:
hive (default)> select name, orderdate, cost
> from
> (
> select name, orderdate, cost,
> ntile(5) over(order by orderdate) ntile_5
> from business
> ) t1
> where t1.ntile_5 = 1;
... ...
OK
name orderdate cost
jack 2017-01-01 10
tony 2017-01-02 15
tony 2017-01-04 29
Time taken: 24.67 seconds, Fetched: 3 row(s)
6.3.6 關於幾個時間參數的使用示例
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 business;
6.3.7 SQL 語句的書寫及執行順序
書寫順序:(gohl)
select
from
join on
where
group by
order by
having
limit
執行順序:(gshol)
from
join on
where
group by
select
having
order by
limit
七、 排名函數
首先要說明的是下面介紹的排名函數也是窗口函數中的, 只不過爲了凸顯它們的排名功能, 所以給單列出來了。
7.1 函數說明
RANK()
: 排序相同時會重複,總數不會變; (有並列第一時是 1, 1, 3, 4, … …)
DENSE_RANK()
: 排序相同時會重複,總數會減少; (有並列第一時是 1, 1, 2, 3, … …)
ROW_NUMBER()
: 會根據順序計算; (有並列第一時也是 1, 2, 3, 4, … …)
7.2 需求
計算每門學科成績排名。
7.3 實現
建表並導入數據:
hive (default)> create table score
> (
> name string,
> subject string,
> score int
> )
> row format delimited fields terminated by '\t';
OK
Time taken: 1.336 seconds
hive (default)> load data local inpath
> '/opt/module/data/subject.txt'
> into table score;
Loading data to table default.score
Table default.score stats: [numFiles=1, totalSize=213]
OK
Time taken: 0.52 seconds
查詢數據:
hive (default)> select * from score;
OK
score.name score.subject score.score
孫悟空 語文 87
孫悟空 數學 95
孫悟空 英語 68
大海 語文 94
大海 數學 56
大海 英語 84
宋宋 語文 64
宋宋 數學 86
宋宋 英語 84
婷婷 語文 65
婷婷 數學 85
婷婷 英語 78
Time taken: 0.094 seconds, Fetched: 12 row(s)
按需求查詢數據:
hive (default)> select name, subject, score,
> rank() over(partition by subject order by score desc) rank_f,
> dense_rank() over(distribute by subject sort by score desc) dense_rank_f,
> row_number() over(partition by subject order by score desc) row_number_f
> from score;
... ...
OK
name subject score rank_f dense_rank_f row_number_f
孫悟空 數學 95 1 1 1
宋宋 數學 86 2 2 2
婷婷 數學 85 3 3 3
大海 數學 56 4 4 4
宋宋 英語 84 1 1 1
大海 英語 84 1 1 2
婷婷 英語 78 3 2 3
孫悟空 英語 68 4 3 4
大海 語文 94 1 1 1
孫悟空 語文 87 2 2 2
婷婷 語文 65 3 3 3
宋宋 語文 64 4 4 4
Time taken: 30.053 seconds, Fetched: 12 row(s)
八、 兩個小題
8.1 統計用戶累計訪問次數
建表及導入數據:
hive (default)> create table action
> (
> userid string,
> visitdate string,
> visitcount int
> )
> row format delimited fields terminated by '\t';
OK
Time taken: 0.079 seconds
hive (default)> load data local inpath
> '/opt/module/data/visit.txt'
> into table action;
Loading data to table default.action
Table default.action stats: [numFiles=1, totalSize=128]
OK
Time taken: 0.206 seconds
查詢數據:
hive (default)> select * from action;
OK
action.userid action.visitdate action.visitcount
u01 2017/1/21 5
u02 2017/1/23 6
u03 2017/1/22 8
u04 2017/1/20 3
u01 2017/1/23 6
u01 2017/2/21 8
u02 2017/1/23 6
u01 2017/2/22 4
Time taken: 0.054 seconds, Fetched: 8 row(s)
需求: 統計出每個用戶、 每個月的累計訪問次數:
首先, 將日期修改爲最終需要的格式:
hive (default)> select userid,
> date_format(regexp_replace(visitdate, '/', '-'), 'yyyy-MM') mn,
> visitcount
> from action;
OK
userid mn visitcount
u01 2017-01 5
u02 2017-01 6
u03 2017-01 8
u04 2017-01 3
u01 2017-01 6
u01 2017-02 8
u02 2017-01 6
u01 2017-02 4
Time taken: 0.065 seconds, Fetched: 8 row(s)
其次, 求出第三列:
hive (default)> select userid, mn, sum(visitcount)
> from
> (
> select userid,
> date_format(regexp_replace(visitdate, '/', '-'),'yyyy-MM') mn,
> visitcount
> from action
> ) t1
> group by userid, mn;
... ...
OK
userid mn _c2
u01 2017-01 11
u01 2017-02 12
u02 2017-01 12
u03 2017-01 8
u04 2017-01 3
Time taken: 17.183 seconds, Fetched: 5 row(s)
最後, 求出第四列:
hive (default)> select userid, mn, sum_c, sum(sum_c) over(distribute by userid sort by mn)
> from
> (
> select userid, mn, sum(visitcount) sum_c
> from (
> select userid,
> date_format(regexp_replace(visitdate, '/', '-'),'yyyy-MM') mn,
> visitcount
> from action) t1
> group by userid, mn
> ) t2;
... ...
OK
userid mn sum_c sum_window_0
u01 2017-01 11 11
u01 2017-02 12 23
u02 2017-01 12 12
u03 2017-01 8 8
u04 2017-01 3 3
Time taken: 15.256 seconds, Fetched: 5 row(s)
8.2 店鋪訪客數等
有 50W個店鋪,每個顧客訪問任何一個店鋪的任何一個商品時都會產生一條訪問日誌,訪問日誌存儲的表名爲 jd, 訪客的用戶 id 爲 user_id, 被訪問的店鋪名稱爲 shop, 請統計:
-
每個店鋪的UV(訪客數)
-
每個店鋪訪問次數top3的訪客信息。輸出店鋪名稱、訪客id、訪問次數
建表及導入數據:
hive (default)> create table jd(
> usr_id string,
> shop string
> )
> row format delimited fields terminated by '\t';
OK
Time taken: 0.679 seconds
hive (default)> load data local inpath
> '/opt/module/data/jd.txt'
> into table jd;
Loading data to table default.jd
Table default.jd stats: [numFiles=1, totalSize=95]
OK
Time taken: 0.34 seconds
查詢數據:
hive (default)> select * from jd;
OK
jd.usr_id jd.shop
u1 a
u2 b
u1 b
u1 a
u3 c
u4 b
u1 a
u2 c
u5 b
u4 b
u6 c
u2 c
u1 b
u2 a
u2 a
u3 a
u5 a
u5 a
u5 a
Time taken: 0.079 seconds, Fetched: 19 row(s)
需求一: 每個店鋪的UV(訪客數)
先按照用戶、商鋪去重:
hive (default)> select usr_id, shop
> from jd
> group by usr_id, shop;
... ...
OK
usr_id shop
u1 a
u1 b
u2 a
u2 b
u2 c
u3 a
u3 c
u4 b
u5 a
u5 b
u6 c
Time taken: 15.159 seconds, Fetched: 11 row(s)
然後計數:
hive (default)> select shop, count(1) uv from
> (
> select usr_id, shop
> from jd
> group by usr_id, shop
> ) t1
> group by shop;
... ...
OK
shop uv
a 4
b 4
c 3
Time taken: 64.018 seconds, Fetched: 3 row(s)
需求二: 每個店鋪訪問次數 top3 的訪客信息。輸出店鋪名稱、訪客id、訪問次數
先計算每個商鋪中每個賬戶的訪問次數:
hive (default)> select shop, usr_id, count(*)
> from jd
> group by shop, usr_id;
... ...
OK
shop usr_id _c2
a u1 3
a u2 2
a u3 1
a u5 3
b u1 2
b u2 1
b u4 2
b u5 1
c u2 2
c u3 1
c u6 1
Time taken: 44.766 seconds, Fetched: 11 row(s)
針對同一店鋪, 對訪問次數進行逆序排序, 並新增一個 rank 排序列:
hive (default)> select shop, usr_id, uv, row_number() over(distribute by shop sort by uv desc)
> from (
> select shop, usr_id, count(*) uv
> from jd
> group by shop, usr_id
> ) t1;
... ...
OK
shop usr_id uv row_number_window_0
a u5 3 1
a u1 3 2
a u2 2 3
a u3 1 4
b u4 2 1
b u1 2 2
b u5 1 3
b u2 1 4
c u2 2 1
c u6 1 2
c u3 1 3
Time taken: 47.539 seconds, Fetched: 11 row(s)
從上一步的數據中, 拿出 top3:
hive (default)> select shop, usr_id, uv
> from
> (
> select shop, usr_id, uv, row_number() over(distribute by shop sort by uv desc) rk
> from (
> select shop, usr_id, count(*) uv
> from jd
> group by shop, usr_id) t1
> ) t2
> where rk < 4;
... ...
OK
shop usr_id uv
a u5 3
a u1 3
a u2 2
b u4 2
b u1 2
b u5 1
c u2 2
c u6 1
c u3 1
Time taken: 73.195 seconds, Fetched: 9 row(s)