Hive 窗口函数

1. 什么是窗口函数

窗口函数是用于分析用的一类函数,要理解窗口函数要先从聚合函数说起。 大家都知道聚合函数是将某列中多行的值合并为一行,比如sum、count等。 而窗口函数则可以在本行内做运算,得到多行的结果,即每一行对应一行的值。 通用的窗口函数可以用下面的语法来概括:

Function() Over (Partition By Column1,Column2,Order By Column3)

窗口函数又分为以下三类:

  • 聚合型窗口函数

  • 分析型窗口函数

  • 取值型窗口函数

接下来我们将通过几个实际的例子来介绍下窗口函数。

2. 准备数据

首先我们准备如下数据:

touch /datas/business.txt

vi business.txt

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

#创建hive表
create table business(
name string, 
orderdate string,
cost int
) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

# 加载数据
load data local inpath "/datas/business.txt" into table business;

3. 聚合型窗口函数

聚合型即SUM(), MIN(),MAX(),AVG(),COUNT()这些常见的聚合函数。 聚合函数配合窗口函数使用可以使计算更加灵活,比如:

(1)查询在2017年4月份购买过的顾客及总人数

select 
	name,
	count(*) over() as num
from business
where substring(orderdate,1,7)='2017-04'
group by name;

查询结果如下:
-----------------------------------------
name	num
mart	2
jack	2
-----------------------------------------

(2)查询顾客的购买明细及每月所有顾客的购买总额

select 
name,orderdate,cost,
sum(cost) over(partition by month(orderdate)) as month_cost
from business;

查询结果如下:
-----------------------------------------
name	orderdate	cost	month_cost
jack	2017-01-01	10	205
jack	2017-01-08	55	205
tony	2017-01-07	50	205
jack	2017-01-05	46	205
tony	2017-01-04	29	205
tony	2017-01-02	15	205
jack	2017-02-03	23	23
mart	2017-04-13	94	341
jack	2017-04-06	42	341
mart	2017-04-11	75	341
mart	2017-04-09	68	341
mart	2017-04-08	62	341
neil	2017-05-10	12	12
neil	2017-06-12	80	80
-----------------------------------------

ps: 如果要统计 顾客的购买明细 以及顾客每月的购买总额

select 
    name,orderdate,cost,
    sum(cost) over(partition by name,month(orderdate)) as cm_cost
from business;

查询结果如下:
-----------------------------------------
name	orderdate	cost	cm_cost
jack	2017-01-05	46	111
jack	2017-01-08	55	111
jack	2017-01-01	10	111
jack	2017-02-03	23	23
jack	2017-04-06	42	42
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	12
neil	2017-06-12	80	80
tony	2017-01-04	29	94
tony	2017-01-02	15	94
tony	2017-01-07	50	94
-----------------------------------------

上述窗口均是整个窗口的统计,实际还可以通过row between 定义窗口的范围,比如指定窗口的起点,终点;

CURRENT ROW:当前行
n PRECEDING:往前n行数据
n FOLLOWING:往后n行数据
UNBOUNDED:起点,UNBOUNDED PRECEDING 表示从前面的起点, UNBOUNDED FOLLOWING表示到后面的终点
(3)上述的场景,要将cost按照日期进行累加
select 
    name,orderdate,cost,
    sum(cost) over(partition by name order by orderdate) as s1,
    sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and current row) as s2, -- 从起点到当前行
    sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING and current row) as s3, -- 从前一行到当前行
    sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING and 1 FOLLOWING) as s4, -- 从前一行到后一行
    sum(cost) over(partition by name order by orderdate rows between current row and UNBOUNDED FOLLOWING) as s5 -- 从当前行到后面所有行
from business;

查询结果如下:
-----------------------------------------
name	orderdate	cost	s1	s2	s3	s4	s5
jack	2017-01-01	10	10	10	10	56	176
jack	2017-01-05	46	56	56	56	111	166
jack	2017-01-08	55	111	111	101	124	120
jack	2017-02-03	23	134	134	78	120	65
jack	2017-04-06	42	176	176	65	65	42
mart	2017-04-08	62	62	62	62	130	299
mart	2017-04-09	68	130	130	130	205	237
mart	2017-04-11	75	205	205	143	237	169
mart	2017-04-13	94	299	299	169	169	94
neil	2017-05-10	12	12	12	12	92	92
neil	2017-06-12	80	92	92	92	92	80
tony	2017-01-02	15	15	15	15	44	94
tony	2017-01-04	29	44	44	44	94	79
tony	2017-01-07	50	94	94	79	79	50
-----------------------------------------

ps: s1默认是整个窗口,和s2指定窗口范围是原始起点和最终终点,是等价的,故结论完全一致;
count(distinct xxx)在窗口函数里是不允许使用的,不过我们也可以用size(collect_set() over(partition by order by))来替代实现我们的需求;

hive (test)> select *,count(distinct name) over(order by orderdate) from business;
FAILED: SemanticException [Error 10025]: Expression not in GROUP BY key name


select 
    name,orderdate,cost,
    size(collect_set(name) over()) as num 
from business order by orderdate;

查询结果如下:
-----------------------------------------
name	orderdate	cost	num
jack	2017-01-01	10	4
tony	2017-01-02	15	4
tony	2017-01-04	29	4
jack	2017-01-05	46	4
tony	2017-01-07	50	4
jack	2017-01-08	55	4
jack	2017-02-03	23	4
jack	2017-04-06	42	4
mart	2017-04-08	62	4
mart	2017-04-09	68	4
mart	2017-04-11	75	4
mart	2017-04-13	94	4
neil	2017-05-10	12	4
neil	2017-06-12	80	4
-----------------------------------------

4. 分析型窗口函数

分析型即 RANK(), ROW_NUMBER(), DENSE_RANK()等常见的排序用的窗口函数,不过他们也是有区别的。

RANK() 排序相同时会重复,总数不会变

DENSE_RANK() 排序相同时会重复,总数会减少

ROW_NUMBER() 会根据顺序计算

除了这三个排序用的函数,还有:

CUME_DIST函数 :小于等于当前值的行在所有行中的占比

PERCENT_RANK() :小于当前值的行在所有行中的占比

NTILE() :如果把数据按行数分为n份,那么该行所属的份数是第几份

NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型。每份的数量分配规则是: 先整除得到平均数,把余数再轮询从前往后分配; 比如共有14条数据,准备分成5组,那么 14/5 = 2 余 4 余数4会被 加入到前4组 所以结果是 3 3 3 3 2

select 
    name,
    subject,
    score,
    rank() over(partition by subject order by score desc) rp,
    dense_rank() over(partition by subject order by score desc) drp,
    row_number() over(partition by subject order by score desc) rmp
from score;

查询结果如下:
-----------------------------------------
name    subject score   rp      drp     rmp
孙悟空  数学    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
-----------------------------------------


select *,ntile(5) over(order by orderdate) as sorted from business;

查询结果如下:
-----------------------------------------
business.name	business.orderdate	business.cost	sorted
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
-----------------------------------------

5. 取值型窗口函数

这几个函数可以通过字面意思记得,LAG是迟滞的意思,也就是对某一列进行往后错行;LEAD是LAG的反义词,也就是对某一列进行提前几行;FIRST_VALUE是对该列到目前为止的首个值,而LAST_VALUE是到目前行为止的最后一个值。

LAG()和LEAD() 可以带3个参数,第一个是返回的值,第二个是前置或者后置的行数,第三个是默认值。

LAG(col,n,defaultValue):往前第n行数据
LEAD(col,n,defaultValue):往后第n行数据

select 
    name,orderdate,cost,
    lag(orderdate,1,'1970-01-01') over(partition by name order by orderdate) as up_time, --顾客上一次购买时间
    lead(orderdate,1,'9999-01-01') over(partition by name order by orderdate) as down_time --顾客下一次购买时间
from business;

查询结果如下:
---------------------------------------------
name	orderdate	cost	up_time	down_time
jack	2017-01-01	10	1970-01-01	2017-01-05
jack	2017-01-05	46	2017-01-01	2017-01-08
jack	2017-01-08	55	2017-01-05	2017-02-03
jack	2017-02-03	23	2017-01-08	2017-04-06
jack	2017-04-06	42	2017-02-03	9999-01-01
mart	2017-04-08	62	1970-01-01	2017-04-09
mart	2017-04-09	68	2017-04-08	2017-04-11
mart	2017-04-11	75	2017-04-09	2017-04-13
mart	2017-04-13	94	2017-04-11	9999-01-01
neil	2017-05-10	12	1970-01-01	2017-06-12
neil	2017-06-12	80	2017-05-10	9999-01-01
tony	2017-01-02	15	1970-01-01	2017-01-04
tony	2017-01-04	29	2017-01-02	2017-01-07
tony	2017-01-07	50	2017-01-04	9999-01-01
----------------------------------------------

6. 总结

通过窗口函数,可以在当前行得到多行数据聚合分析的结果数据,便于后续进一步分析处理。

本文参考链接: https://zhuanlan.zhihu.com/p/77705681

posted on 2020-06-23 10:58  一剑风徽  阅读(282)  评论(0编辑  收藏  举报

导航