4 Window functions 窗口函数
4.1 窗口函数概述
窗口函数(Window functions)是一种SQL函数,非常适合于数据分析,因此也叫做OLAP函数,其最大特点是:输入值是从SELECT语句的结果集中的一行或多行的“窗口”中获取的。你也可以理解为窗口有大有小(行有多有少)。
通过OVER子句,窗口函数与其他SQL函数有所区别。如果函数具有OVER子句,则它是窗口函数。如果它缺少OVER子句,则它是一个普通的聚合函数。
窗口函数可以简单地解释为类似于聚合函数的计算函数,但是通过GROUP BY子句组合的常规聚合会隐藏正在聚合的各个行,最终输出一行,窗口函数聚合后还可以访问当中的各个行,并且可以将这些行中的某些属性添加到结果集中。
为了更加直观感受窗口函数,我们通过sum聚合函数进行普通常规聚合和窗口聚合,一看效果。
----sum+group by普通常规聚合操作------------ select sum(salary) as total from employee group by dept; ----sum+窗口函数聚合操作------------ select id,name,deg,salary,dept,sum(salary) over(partition by dept) as total from employee;
4.2 窗口函数语法
Function(arg1,..., argn) OVER ([PARTITION BY <...>] [ORDER BY <....>]
[<window_expression>])
--其中Function(arg1,..., argn) 可以是下面分类中的任意一个 --聚合函数:比如sum max avg等 --排序函数:比如rank row_number等 --分析函数:比如lead lag first_value等 --OVER [PARTITION BY <...>] 类似于group by 用于指定分组 每个分组你可以把它叫做窗口 --如果没有PARTITION BY 那么整张表的所有行就是一组 --[ORDER BY <....>] 用于指定每个分组内的数据排序规则 支持ASC、DESC --[<window_expression>] 用于指定每个窗口中 操作的数据范围 默认是窗口中所有行
4.3 案例:网站用户页面浏览次数分析
在网站访问中,经常使用cookie来标识不同的用户身份,通过cookie可以追踪不同用户的页面访问情况,有下面两份数据:
字段含义:cookieid 、访问时间、pv数(页面浏览数)
字段含义:cookieid、访问时间、访问页面url
在Hive中创建两张表表,把数据加载进去用于窗口分析。
---建表并且加载数据 create table website_pv_info( cookieid string, createtime string, --day pv int ) row format delimited fields terminated by ','; create table website_url_info ( cookieid string, createtime string, --访问时间 url string --访问页面 ) row format delimited fields terminated by ','; load data local inpath '/root/hivedata/website_pv_info.txt' into table website_pv_info; load data local inpath '/root/hivedata/website_url_info.txt' into table website_url_info; select * from website_pv_info; select * from website_url_info;
4.3.1 窗口聚合函数
从Hive v2.2.0开始,支持DISTINCT与窗口函数中的聚合函数一起使用。
这里以sum()函数为例,其他聚合函数使用类似。
-----窗口聚合函数的使用----------- --1、求出每个用户总pv数 sum+group by普通常规聚合操作 select cookieid,sum(pv) as total_pv from website_pv_info group by cookieid; --2、sum+窗口函数 总共有四种用法 注意是整体聚合 还是累积聚合 --sum(...) over( )对表所有行求和 --sum(...) over( order by ... ) 连续累积求和 --sum(...) over( partition by... ) 同组内所有行求和 --sum(...) over( partition by... order by ... ) 在每个分组内,连续累积求和 --需求:求出网站总的pv数 所有用户所有访问加起来 --sum(...) over( )对表所有行求和 select cookieid,createtime,pv, sum(pv) over() as total_pv from website_pv_info; --需求:求出每个用户总pv数 --sum(...) over( partition by... ),同组内所行求和 select cookieid,createtime,pv, sum(pv) over(partition by cookieid) as total_pv from website_pv_info; --需求:求出每个用户截止到当天,累积的总pv数 --sum(...) over( partition by... order by ... ),在每个分组内,连续累积求和 select cookieid,createtime,pv, sum(pv) over(partition by cookieid order by createtime) as current_total_pv from website_pv_info;
4.3.2 窗口表达式
我们知道,在sum(…) over( partition by… order by … )语法完整的情况下,进行的累积聚合操作,默认累积聚合行为是:从第一行聚合到当前行。
Window expression窗口表达式给我们提供了一种控制行范围的能力,比如向前2行,向后3行。
语法如下:
关键字是rows between,包括下面这几个选项
- preceding:往前 - following:往后 - current row:当前行 - unbounded:边界 - unbounded preceding 表示从前面的起点 - unbounded following:表示到后面的终点 ---窗口表达式 --第一行到当前行 select cookieid,createtime,pv, sum(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row) as pv2 from website_pv_info; --向前3行至当前行 select cookieid,createtime,pv, sum(pv) over(partition by cookieid order by createtime rows between 3 preceding and current row) as pv4 from website_pv_info; --向前3行 向后1行 select cookieid,createtime,pv, sum(pv) over(partition by cookieid order by createtime rows between 3 preceding and 1 following) as pv5 from website_pv_info; --当前行至最后一行 select cookieid,createtime,pv, sum(pv) over(partition by cookieid order by createtime rows between current row and unbounded following) as pv6 from website_pv_info; --第一行到最后一行 也就是分组内的所有行 select cookieid,createtime,pv, sum(pv) over(partition by cookieid order by createtime rows between unbounded preceding and unbounded following) as pv6 from website_pv_info;
4.3.3 窗口排序函数
窗口排序函数用于给每个分组内的数据打上排序的标号。注意窗口排序函数不支持窗口表达式。总共有4个函数需要掌握:
row_number:在每个分组中,为每行分配一个从1开始的唯一序列号,递增,不考虑重复;
rank: 在每个分组中,为每行分配一个从1开始的序列号,考虑重复,挤占后续位置;
dense_rank: 在每个分组中,为每行分配一个从1开始的序列号,考虑重复,不挤占后续位置;
-----窗口排序函数 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 website_pv_info WHERE cookieid = 'cookie1';
述这三个函数用于分组TopN的场景非常适合。
--需求:找出每个用户访问pv最多的Top3 重复并列的不考虑 SELECT * from (SELECT cookieid, createtime, pv, ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY pv DESC) AS seq FROM website_pv_info) tmp where tmp.seq <4;
还有一个函数,叫做ntile函数,其功能为:将每个分组内的数据分为指定的若干个桶里(分为若干个部分),并且为每一个桶分配一个桶编号。
如果不能平均分配,则优先分配较小编号的桶,并且各个桶中能放的行数最多相差1。
有时会有这样的需求:如果数据排序后分为三部分,业务人员只关心其中的一部分,如何将这中间的三分之一数据拿出来呢?NTILE函数即可以满足。
--把每个分组内的数据分为3桶 SELECT cookieid, createtime, pv, NTILE(3) OVER(PARTITION BY cookieid ORDER BY createtime) AS rn2 FROM website_pv_info ORDER BY cookieid,createtime;
--需求:统计每个用户pv数最多的前3分之1天。 --理解:将数据根据cookieid分 根据pv倒序排序 排序之后分为3个部分 取第一部分 SELECT * from (SELECT cookieid, createtime, pv, NTILE(3) OVER(PARTITION BY cookieid ORDER BY pv DESC) AS rn FROM website_pv_info) tmp where rn =1;
4.3.4 窗口分析函数
LAG(col,n,DEFAULT) 用于统计窗口内往上第n行值
第一个参数为列名,第二个参数为往上第n行(可选,默认为1),第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL);
LEAD(col,n,DEFAULT) 用于统计窗口内往下第n行值
第一个参数为列名,第二个参数为往下第n行(可选,默认为1),第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL);
FIRST_VALUE 取分组内排序后,截止到当前行,第一个值;
LAST_VALUE 取分组内排序后,截止到当前行,最后一个值;
-----------窗口分析函数---------- --LAG SELECT cookieid, createtime, url, ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY createtime) AS rn, LAG(createtime,1,'1970-01-01 00:00:00') OVER(PARTITION BY cookieid ORDER BY createtime) AS last_1_time, LAG(createtime,2) OVER(PARTITION BY cookieid ORDER BY createtime) AS last_2_time FROM website_url_info; --LEAD SELECT cookieid, createtime, url, ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY createtime) AS rn, LEAD(createtime,1,'1970-01-01 00:00:00') OVER(PARTITION BY cookieid ORDER BY createtime) AS next_1_time, LEAD(createtime,2) OVER(PARTITION BY cookieid ORDER BY createtime) AS next_2_time FROM website_url_info; --FIRST_VALUE SELECT cookieid, createtime, url, ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY createtime) AS rn, FIRST_VALUE(url) OVER(PARTITION BY cookieid ORDER BY createtime) AS first1 FROM website_url_info; --LAST_VALUE SELECT cookieid, createtime, url, ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY createtime) AS rn, LAST_VALUE(url) OVER(PARTITION BY cookieid ORDER BY createtime) AS last1 FROM website_url_info;
5 Sampling 抽样函数
5.1 抽样概述
当数据量过大时,我们可能需要查找数据子集以加快数据处理速度分析。 这就是抽样、采样,一种用于识别和分析数据中的子集的技术,以发现整个数据集中的模式和趋势。
在HQL中,可以通过三种方式采样数据:随机采样,存储桶表采样和块采样。
5.2 Random随机抽样
随机抽样使用rand()函数和LIMIT关键字来获取数据。 使用了DISTRIBUTE和SORT关键字,可以确保数据也随机分布在mapper和reducer之间,使得底层执行有效率。
ORDER BY 和rand()语句也可以达到相同的目的,但是表现不好。因为ORDER BY是全局排序,只会启动运行一个Reducer。
--数据表 select * from student; --需求:随机抽取2个学生的情况进行查看 SELECT * FROM student DISTRIBUTE BY rand() SORT BY rand() LIMIT 2; --使用order by+rand也可以实现同样的效果 但是效率不高 SELECT * FROM student ORDER BY rand() LIMIT 2;
5.3 Block块抽样
Block块采样允许select随机获取n行数据,即数据大小或n个字节的数据。
采样粒度是HDFS块大小。
---block抽样 --根据行数抽样 SELECT * FROM student TABLESAMPLE(1 ROWS); --根据数据大小百分比抽样 SELECT * FROM student TABLESAMPLE(50 PERCENT); --根据数据大小抽样 --支持数据单位 b/B, k/K, m/M, g/G SELECT * FROM student TABLESAMPLE(1k);
5.4 Bucket table分桶表抽样
这是一种特殊的采样方法,针对分桶表进行了优化。
---bucket table抽样 --根据整行数据进行抽样 SELECT * FROM t_usa_covid19_bucket TABLESAMPLE(BUCKET 1 OUT OF 2 ON rand()); --根据分桶字段进行抽样 效率更高 describe formatted t_usa_covid19_bucket; SELECT * FROM t_usa_covid19_bucket TABLESAMPLE(BUCKET 1 OUT OF 2 ON state);