HIve函数—窗口函数

HIve函数—窗口函数

本文记录了hive中窗口函数的使用及一些简单案例


提示:以下是本篇文章正文内容,下面案例可供参考


1. 概述

  1. 窗口函数即是对查询的结果集的行数据进行计算,也就是说开的这个窗口大小可能会随着行数据的变化而变化;
  2. 窗口函数需要特殊的关键字OVER子句来指定窗口即触发一个窗口函数;
  3. over() 往往跟在聚合函数后面,即类似开了一个窗口,截取了一部分数据集出来,给前面的聚合函数使用。

说明:窗口函数不同于我们熟悉的普通函数和聚合函数:窗口函数对于每个组返回多行,即组内每一行会对应返回一行值,也就是说对行数据进行处理;而聚合函数是对列中的一系列数据进行处理,对于每个组只返回一行

2. 相关函数说明

  1. OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化,over()可传参
  2. CURRENT ROW:当前行
  3. n PRECEDING:往前n行数据
  4. n FOLLOWING:往后n行数据
  5. UNBOUNDED:起点,UNBOUNDED PRECEDING 表示从前面的起点
  6. UNBOUNDED FOLLOWING表示到后面的终点
  7. LAG(col,n):往前n行数据
  8. LEAD(col,n):往后n行数据
  9. NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型

2. 简单案例

案例需求如下:
(1)查询在2017年4月份购买过的顾客及总人数
(2)查询顾客的购买明细及月购买总额
(3)将cost按照日期进行累加
(4)查询顾客上次的购买时间
(5)查询前20%时间的订单信息

2.1 数据准备

不同用户在不同时间消费的金额(以‘,’为分隔符)
name,orderdate,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

2.3 建表、导入数据

进入hive的客户端
在hive的home目录下的datas目录创建business.txt,准备源数据

[root@Master datas]# vi business.txt

创建hive表business,并指定分隔符 ’,‘

create table business(
name string, 
orderdate string,
cost int
) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

导入数据,并且可以查看下导入的数据

hive (default)> load data local inpath "./datas/business.txt" into table business;
hive (default)> select * from business;

在这里插入图片描述

3. 完成需求

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

我们可以先看看如果不用窗口函数,简单聚合,即不加 over()子句的效果
代码如下(示例):

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

结果:

name	count
jack	1
mart	4

在这里插入图片描述

可以看出此条sql语句求的需求,其实是求2017年4月份购买过的用户及其购买的次数,而不是总人数,这是对用户分组后的每个用户购买记录次数的聚合,而不是我们本次的需求,求总人数,也就是说,我们预期的效果应该是:

当月购买的用户名字		当月购买的总人数
jack				2
mart				2

实现需求,我们加个over()子句看看
代码如下(示例):

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

结果:

可以看出,加了个over()子句,完成需求,并且,虽然我们没有给count的结果别名,但系统也为我们自动分配了列名 count_window_0

name	count_window_0
mart	2
jack	2

在这里插入图片描述

分析:

  1. 搞清楚需求,需求是让我们求当月购买商品的用户及统计当月购买商品总人数,也就是说有多少个人买了,而不是每个用户购买的总次数,后者好实现,只需分组求和即可,统计每个用户的记录数即可

  2. 当然也会有小伙伴会说,我们需求也是可以分组求和,即对分组后形成的用户那一列求和,思路是对的,而且当时我也是这样想的,如下:
    代码如下(示例):

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

    在这里插入图片描述
    但你会发现,这会报错,提示我们Invalid table alias or column reference 'group_name': (possible column names are: name, orderdate, cost),意思是说是无效的表别名或列引用group_name,其实很容易理解,count()聚合函数用法,如下:

    1. COUNT(column_name) 函数返回指定列的值的数目(NULL 不计入)
    2. COUNT(*) 函数返回表中的记录数
    3. COUNT(DISTINCT column_name) 函数返回指定列的不同值的数目

    可以发现聚合函数,一般情况下,统计的是数据集是表business,并且其统计的列(名)在表business中存在的,我们通过group by分组后,如果想要进行统计,那么其关联原表的是列name,而不是group_name,group_name只是在分组后数据取得别名,而表business不存在列group_name,所以这就是为什么提示无效的引用group_name,当然如果刚刚那条sql语句改为如下,是可以通过的,因为这个跟我们一开始的一样,只是加了个别名,统计的仍然是购买的次数,而不是人数
    代码如下(示例):

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

    在这里插入图片描述

  3. 在概述中说明了何为窗口函数,窗口函数即是对查询的结果集的行数据进行计算,那么在这里可以这样通俗理解,即group by分组后产生得结果集,因为遇到了over(),那么它就会开一个窗口,这个窗口中包含聚合函数count()和查询出来得结果集,所以当count(*),统计记录数时,是以这个结果集为基准,所以group by分组后,只剩下jack跟mart,所以统计出来,人数为2

哈哈哈,不知道大家有没有get到,也有可能被我绕晕,不明白的可以多看几遍,动手去修改下sql,把能想到的情况一一列出来,对比,例如,以下这种情况:
我们仍然是统计2017年4月份购买过的顾客及当月的总人次,所以那么这里就不用分组了,因为只要分组过滤出来就可以统计了:
代码如下(示例):

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

结果:

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

在这里插入图片描述

然后再试试下面这条sql,与上条sql语句区别,仅仅是少了一个over() 子句
代码如下(示例):

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

在这里插入图片描述
这个报错是很正常的,因为这个正确的写法,就是我们日常写的分组聚合函数语句,就是需要group by

希望没有把你们绕晕~

3.2 查询顾客的购买明细及月购买总额

又是一个看不懂需求的情况,每个字明明看得懂,但连在起来就是看不懂系列
先看看原始数据
name,orderdate,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

分析:

  1. 需求让我们达到的效果是什么,比如有哪些用户在1月份购买了东西,把是谁在什么时候花了多少打印出来,然后月购买总额,就是在1月份中这些用户买东西,花了多少打印出来,即预期效果应该是下面这样的

    name	orderdate	cost  sum
    jack	2017-01-01	10	  54   
    tony	2017-01-04	29    543条为1月份
    tony	2017-01-02	15    54
    
    jack	2017-02-03	23	  23	此条为1月份
    
    jack	2017-04-06	48    1102条为4月份  
    mart	2017-04-08	62    110
    
    neil	2017-05-10	12    302条为5月份
    neil	2017-05-17	18	  30
    
  2. 也就是说,需要分组原则是月份(日期)

    代码如下(示例):

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

    结果:
    在这里插入图片描述

3.3 将cost按照日期进行累加

分析:即先将日期排序,然后将前几天花了少钱进行相加,例如:

orderdate	cost	sum
01-01		1		1
02-02		2		3
03-03		3		6

代码如下(示例):

select name,orderdate,cost,sum(cost) over(distribute by month(orderdate) sort by orderdate) 
from business;

结果:
在这里插入图片描述

再修改下需求,即这个累加,是每个用户按照购买日期,cost的累加,这里是以用户为组
以下两条都可以实现,distribute by、partition by都有分区概念,但前者是和sort by连用,后者是和order by连用,不能交叉使用,并且要注意的是:有order by的情况,窗口范围是从第一条数据开始,到本条数据
代码如下(示例):

select name,orderdate,cost,sum(cost) over(distribute by name sort by orderdate) 
from business;
select name,orderdate,cost,sum(cost) over(partition by name order by orderdate) 
from business;

结果:
在这里插入图片描述

3.4 查询顾客上次的购买时间(lag、lead函数)

(lag、lead函数->不写在over()内)
用到 LAG(col,n,[default])函数:往前n行数据,col -> 需要查看的列,n -> 第n行数据,default -> 如果没有,则取给定的默认值,[default]是可选参数,如果不传,则打印NULL
代码如下(示例):

select name,orderdate,cost, 
lag(orderdate,1,'1900-01-01') over(partition by name order by orderdate ) as time
from business;

结果:
在这里插入图片描述在这里插入图片描述

LEAD(col,n,[default]):往后第n行,用法与lag一样
代码如下(示例):

select name,orderdate,cost, 
lead(orderdate,1,'1900-01-01') over(partition by name order by orderdate ) as time
from business;

结果:
在这里插入图片描述

3.5 查询前20%时间的订单信息(ntile函数)

用到NTILE(n)函数
NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型。
需求分析:即将时间进行排序,并把排好序的数据取前20%,也就是前1/5,所以用NTILE()函数对数据进行平均等分为5分,并编号1,2,3,4,5,然后取第1份的数据
代码如下(示例):

select * from (
    select name,orderdate,cost, ntile(5) over(order by orderdate) sorted
    from business
) t
where sorted = 1;

结果:
在这里插入图片描述

总的数据为:
代码如下(示例):

select * from (
    select name,orderdate,cost, ntile(5) over(order by orderdate) sorted
    from business
) t;

结果:
在这里插入图片描述

4. 其它函数案例(限定数据集)

  1. 起点 -> 当前行,当前行 -> 末尾,如果有分组,则这个起点、末尾是组内区间的起点和末尾
    CURRENT ROW:当前行 与 UNBOUNDED FOLLOWING:表示到后面的终点,统计每个用户当前行 —> 最后一行的购买金额
    代码如下(示例):

    select name,orderdate,cost, 
    sum(cost) over(partition by name order by orderdate rows between current row and UNBOUNDED FOLLOWING ) current_following
    from business;
    

    结果:
    在这里插入图片描述

    UNBOUNDED PRECEDING:从前面的起点 —> 当前行

    代码如下(示例):

    select name,orderdate,cost, 
    sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and current row ) current_following
    from business;
    

    结果:
    在这里插入图片描述

  2. 往前、往后几行(如果有分组,那么累加也是对仅在分组区间内的数据生效)
    统计往前一行 -> 当前行
    代码如下(示例):

    select name,orderdate,cost, 
    sum(cost) over(partition by name order by orderdate rows between 1 PRECEDING and current row) as sum
    from business;
    

    结果:
    在这里插入图片描述

    统计当前行 -> 往后一行

    代码如下(示例):

    select name,orderdate,cost, 
    sum(cost) over(partition by name order by orderdate rows between current row AND 1 FOLLOWING ) as sum
    from business;
    

    结果:
    在这里插入图片描述

5. Rank函数

rank函数也是窗口函数,因为也是需要用到over()子句

  1. 函数说明
    RANK() 排序相同时会重复,总数不会变(1,1,3,4)
    DENSE_RANK() 排序相同时会重复,总数会减少(1,1,2,3)
    ROW_NUMBER() 会根据顺序计算(1,2,3,4)
  2. 数据准备
    唐僧师徒四人各科成绩
    孙悟空	语文	87
    孙悟空	数学	95
    孙悟空	英语	68
    猪八戒	语文	94
    猪八戒	数学	56
    猪八戒	英语	84
    唐僧	语文	64
    唐僧	数学	86
    唐僧	英语	84
    沙悟净	语文	65
    沙悟净	数学	85
    沙悟净	英语	78
    
  3. 创表、导入数据
    代码如下(示例):
    create table score(
    name string,
    subject string, 
    score int) 
    row format delimited fields terminated by "\t";
    
    load data local inpath './datas/score.txt' into table score;
    
    在这里插入图片描述
  4. 函数应用
    代码如下(示例):
    select name,
    subject,
    score,
    rank() over(partition by subject order by score desc) rank,
    dense_rank() over(partition by subject order by score desc) dense_rank,
    row_number() over(partition by subject order by score desc) row_number
    from score;
    
    结果:
    在这里插入图片描述

总结

文章也是仅作知识点的记录,欢迎大家指出错误,如有新的感悟也会一并更新,一起探讨~~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值