草庐IT

Hive/Presto中函数grouping sets用法详解(踩坑总结,看到赚到)

LdyLLLLLLLD 2024-02-10 原文

目录


本文详细记录了函数grouping sets使用时遇到的坑,全文代码基于Hive和Presto实现。

1. 问题讨论

1.1 数据准备

首先建立商品销售表:

CREATE TABLE temp.goods_sale_info(  
	`province`   string comment '省份',
	`city`       string comment '城市',
    `goodsid`    string comment '商品编号',
    `goodsname`  string comment '商品名称',
    `sales_qty`  decimal(38,5) comment '销量',
    `sales_amt`  decimal(38,5) comment '销售额'
    ) 
    ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

insert into table temp.goods_sale_info 
values ('江西省','南昌市','1000','可口可乐','10','40')
        ,('福建省','福州市','1000','可口可乐','200','1000')
        ,('福建省','厦门市','1000','可口可乐','300','1500')
        ,('福建省','厦门市','1200','百事可乐','200','1000')
        ,('福建省','厦门市','2000','伊利安慕希','300','21000');

select * from temp.goods_sale_info

再建立一个商品信息表:

CREATE TABLE temp.goods_info(  
    `goodsid`       string comment '商品编号',
    `goodsname`     string comment '商品名称',
    `catgory_id`    string comment '商品种类编号',
    `catgory_name`  string comment '商品种类'
    ) 
    ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

insert into table temp.goods_info
values ('1000','可口可乐','11','饮料')
    ,('1200','百事可乐','11','饮料')
    ,('2000','伊利安慕希','12','乳制品')

select * from temp.goods_info

1.2 问题描述

根据不同字段分组聚合实现各个维度的销量和销售额

1.3 其它方法多维度聚合(union、with cube)

要实现城市’'city’维度聚合求商品销量、销售额以及从省份’province’维度聚合求商品销量、销售额,并放入一个表中,一般可以采用下面两种方法:

-- 方法1:分开聚合
select province
	,NULL as city
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,'prov_dim' dim_flag
from temp.goods_sale_info
group by province,goodsid,goodsname
union all
select NULL as province
	,city
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,'city_dim' dim_flag
from temp.goods_sale_info
group by city,goodsid,goodsname

运行结果:

-- 方法2:with cube高级别聚合
create table temp.goods_sales_info_cube as -- 要生成表才可以进行下一步筛选,起表别名后嵌套筛选不出来
select province
	,city
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,grouping__id --,可写可不写
from temp.goods_sale_info
group by 
	province
	,city
	,goodsid
	,goodsname with cube
-- rollup 是以最左侧指标为主进行组合聚合,是CUBE的子集,以最左侧的维度为主,从该维度进行层级聚合。更换province和city的前后会出来不同结果
-- rollup是cube的一种特殊情况,cube对所有的维度进行聚合,会出现city,province和province,city的结果

-- 对生成的结果进行筛选,即可得到与方法1同样的结果
select *
from temp.goods_sales_info_cube
where grouping__id in ('13','14')
-- 不想生成grouping__id字段的话可以用下面条件判断
-- where (province is null      and city is not null  and goodsid is not null and goodsname is not null)
--     or (province is not null  and city is null      and goodsid is not null and goodsname is not null)

运行结果:

相较于with rollup 和with cube,grouping sets可以实现分组字段的自由组合,当分组字段变多,想要按需分组时候用grouping sets更方便。

2. Hive中的grouping sets函数

对于多个维度聚合问题,grouping sets不用像cube方式将分组字段排列组合列出全部维度的结果,能够实现更灵活的组合。

2.1 grouping sets方法多维度聚合

select province
	,city
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,GROUPING__ID
from temp.goods_sale_info
group by province
	,city
	,goodsid
	,goodsname
	grouping sets(
	(),                            -- 1、全国销量、销售额
	(goodsid,goodsname),           -- 2、各商品的全国销量、销售额
	(province,goodsid,goodsname),  -- 3、各省各商品的销量、销售额
	(province,goodsid),            -- 4、各省各商品的销量、销售额
	(city)                         -- 5、各城市销量、销售额
	)

运行结果

函数及运行结果讲解
1、group by后面放的字段表示要分组聚合的全部字段
2、grouping sets 后面放的是 group by 后面各种字段的组合,根据实际需求进行组合,组合字段用小括号括起来(上面代码中的第2、3、4组),也可以是单一字段(上面代码中sets里面的第5组
3、在求取全国的成交量的时候其实是不需要分组聚合的,但是为了使用 grouping sets,所以在求取全国成交量的时候用 group by null(上面代码中sets里面的第1组
4、sets中第3组第4组的区别在于有没有写goodsname,没有写goodsname的第4组生成的结果中goodsname列为空值

2.2 grouping sets在联结join中使用的踩坑点

在具有表联结语句中grouping sets函数使用有个踩坑点,初次使用很可能都报错找不到原因。。总结一下就是:
表联结的结果出来之后再用grouping sets,分组字段组合sets里面用表别名会报错。

报错语句见下:

-- 报错语句
select t1.province
	,t1.city
	,t2.catgory_id
	,t2.catgory_name
	,t1.goodsid
	,t1.goodsname
	,sum(t1.sales_qty) as sales_qty
	,sum(t1.sales_amt) as sales_amt
	,GROUPING__ID
from temp.goods_sale_info t1
left join
temp.goods_info t2
on t1.goodsid=t2.goodsid
group by 
	t1.province
	,t1.city
	,t2.catgory_id
	,t2.catgory_name
	,t1.goodsid
	,t1.goodsname
	grouping sets(
	(t1.province,t2.catgory_id,t2.catgory_name)
	)

正确语句见下:

-- 正确语句
select province
	,city
	,catgory_id
	,catgory_name
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,GROUPING__ID
from 
(
select t1.province
	,t1.city
	,t2.catgory_id
	,t2.catgory_name
	,t1.goodsid
	,t1.goodsname
	,sales_qty
	,sales_amt
from temp.goods_sale_info t1
left join
temp.goods_info t2
on t1.goodsid=t2.goodsid
) t  --在表t的基础上使用grouping sets函数
group by 
	province
	,city
	,catgory_id
	,catgory_name
	,goodsid
	,goodsname
	grouping sets(
	(province,catgory_id,catgory_name) 
	--这里仅有3个字段,但select中列有不在sets中的非分组字段city、goodsid、goodsname,hive不报错,presto会报错
	)

运行结果:

2.3 grouping sets函数使用补充事项

1、select子句中的GROUPING__ID是两个下划线
2、执行语句后GROUPING__ID的结果是数字,如果sets中有5种组合,GROUPING__ID会生成5个不等的数字,具体哪个数字对应哪个维度,需要根据生成的表进行测试判断,或者根据下面2.4节的方法用二进制去计算确定,用于自定义划分维度
3、GROUPING__ID的数字只要不改变表以及查询条件,每次运行都是这些数字

2.4 计算grouping__id值

方法描述:
grouping sets 中的每一种粒度,都对应唯一的 grouping__id 值,其计算公式与 group by 的顺序、当前粒度的字段有关。

具体计算方法如下:
1、字段排序
将字段按照group by后的顺序 进行排序,例如是3个字段
2、字段赋值
根据grouping sets的每个set中的字段,用0或1进行赋值,例如group by后跟了3个字段结果就会给出一个有3个数字的二进制数。
赋值规则为 对于每个字段,如果该字段有值则赋给0,空值则给1。出现在当前粒度中,则给0,没出现则给1
3、转化十进制
上面赋值后就形成了一个二进制数,然后将二进制转为10进制就是grouping__id的值

下面为了不进行数据测试,直接根据grouping__id的值来判断grouping__id对应的是哪个维度,在select语句中可以按照如下方式判断:
4、判断维度
按照group by字段顺序求 数字2 的幂次方,紧挨着group by的字段为最高次方,依次递减,然后对grouping__id和 相应层级的幂次方 用位运算 ”&与“ 来判断维度

举个例子:

select province
	,city
	,goods_id
	,if(grouping__id & cast(pow(2,2) as int) =0,'province','') as dim
	,sum(sales_amt)
from tmp.table1
group by 
	province
	,city
	,goods_id
	grouping sets(
	(province,city)
	,(province)
	) 

第一步:按顺序,group by 后面是province,city,goods_id,
第二步:以sets中的province为例,得到的二进制数是 011
第三步:转化为十进制,011 为 1*20+1*21+0 =3
第四步:根据province,city,goods_id的顺序,分别为 2的平方、2的1次方、2的0次方
在select中想要判断出province维度,即可用 2的平方 去做位运算, 2的平方对应的二进制为 100
对比
0 1 1
1 0 0
其位运算结果为0
则该grouping__id 对应的是province维度

tips:
跟hive的版本有关,有的版本使用下面的规则:
1、字段排序:将 group by 后面的字段 倒序 排列
2、字段赋值:对于每个字段,如果该字段出现在了当前粒度中,则该字段位置赋值为1,否则为0。
3、转化十进制:将这个二进制数转为十进制,即为当前粒度对应的 grouping__id

3. Presto中的grouping sets函数

3.1 函数grouping sets使用及坑点(5点说明)

用法:
1、函数grouping要与group by、grouping sets配合使用
2、函数grouping()中列出sets中所有分组涉及的字段,运行后grouing()列生成结果为二进制转化来的十进制数字

-- 比如
	select column1,column2,column3,
	grouping(column1,column2,column3)
		group by
			grouping sets(
			(column1),
			(column2,column3)-- 如果分组中包含相应的列,则将位设置为0,否则将其设置为1
-- 第一组 中 三个字段的 为 0 1 1
-- 第二组 中 三个字段的 为 1 0 0
-- grouping函数的值即为011和100对应的十进制数字

参考链接:presto grouping操作

3、group by后面只跟grouping sets(),不加select中的单一字段,否则函数grouping sets无作用

-- 如果group by写上单一字段
select province
	,city
	,catgory_id
	,catgory_name
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,grouping(province,city,catgory_id,catgory_name,goodsid,goodsname)
from 
(
select t1.province
	,t1.city
	,t2.catgory_id
	,t2.catgory_name
	,t1.goodsid
	,t1.goodsname
	,sales_qty
	,sales_amt
from temp.goods_sale_info t1
left join
temp.goods_info t2
on t1.goodsid=t2.goodsid
) t  --也是要在表t的基础上使用grouping sets函数
group by 
    province
	,city
	,catgory_id
	,catgory_name
	,goodsid
	,goodsname
    ,grouping sets( --这里记得加上逗号,
	(province,catgory_id,catgory_name),
	(province,catgory_id,catgory_name,goodsid,goodsname),
	(province,city),
	(province)
	)

运行结果:
表t结果有5条记录,grouping sets中有4种组合。按照上面语句执行,结果记录数为 5×4=20条记录,同理1种组合结果为5×1,2种组合为5×2,3种组合为5×3

4、不用的分组字段不要在select子句中写出

-- 与hive不同,如果不出现在grouping sets中的字段,select子句写上会报错
-- 比如sets中不涉及city、goodsid、goodsname,select子句中写出来报错
select province
-- 	,city
	,catgory_id
	,catgory_name
-- 	,goodsid
-- 	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,grouping(province,catgory_id,catgory_name)
from 
(
select t1.province
	,t1.city
	,t2.catgory_id
	,t2.catgory_name
	,t1.goodsid
	,t1.goodsname
	,sales_qty
	,sales_amt
from temp.goods_sale_info t1
left join
temp.goods_info t2
on t1.goodsid=t2.goodsid
) t
group by 
	grouping sets(
	(province,catgory_id,catgory_name)
	)

5、函数grouping中要将grouping sets所有分组组合用到的字段取并集列出

select province
	,city
	,catgory_id
	,catgory_name
	,goodsid
	,goodsname
	,sum(sales_qty) as sales_qty
	,sum(sales_amt) as sales_amt
	,grouping(province,city,catgory_id,catgory_name,goodsid,goodsname)
	-- grouping里面一定要把sets中用到的字段写全,不然生成的数字会缺
	-- 比如下面sets中有3种组合,如果grouping()种缺少字段,数字会不是3个
from 
(
select t1.province
	,t1.city
	,t2.catgory_id
	,t2.catgory_name
	,t1.goodsid
	,t1.goodsname
	,sales_qty
	,sales_amt
from temp.goods_sale_info t1
left join
temp.goods_info t2
on t1.goodsid=t2.goodsid
) t  --也是要在表t的基础上使用grouping sets函数
group by 
	grouping sets(
	(province,catgory_id,catgory_name),
	(province,catgory_id,catgory_name,goodsid,goodsname),
	(province,city)
	)

3.2 函数grouping sets在hive与presto中的区别

综上可见,可以对比出presto与hive在grouping sets函数使用上的区别:
1、Hive中select子句中用GROUPING__ID,GROUPING__ID不是函数;Presto的select子句中grouping是一个函数,要采用grouping(column_1,column_2,…),列出分组涉及到的所有字段。不过两者运行后的结果都是数字,可以用于后面的维度测试
2、Hive的group by子句中要列出单一字段,然后加上grouping sets,并且grouping sets 前面不加逗号“,”;Presto的group by子句中仅有grouping sets

有关Hive/Presto中函数grouping sets用法详解(踩坑总结,看到赚到)的更多相关文章

  1. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  2. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  3. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  4. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  5. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  6. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  7. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  8. ruby-on-rails - 将字符串转换为 ruby​​-on-rails 中的函数 - 2

    我需要一个通过输入字符串进行计算的方法,像这样function="(a/b)*100"a=25b=50function.something>>50有什么方法吗? 最佳答案 您可以使用instance_eval:function="(a/b)*100"a=25.0b=50instance_evalfunction#=>50.0请注意,使用eval本质上是不安全的,尤其是当您使用外部输入时,因为它可能包含注入(inject)的恶意代码。另请注意,a设置为25.0而不是25,因为如果它是整数a/b将导致0(整数)。

  9. ruby-on-rails - 您希望看到哪些 Rails 插件? - 2

    您认为可以作为插件很好地存在于您的Rails应用程序中必须实现的哪些行为?您过去曾搜索过哪些插件功能但找不到?哪些现有的Rails插件可以改进或扩展,如何改进或扩展? 最佳答案 我希望在管理界面中看到一个引擎插件,它提供了应用程序中所有模型的仪表板摘要,以及可配置的事件图表。 关于ruby-on-rails-您希望看到哪些Rails插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questio

  10. ruby - 在 ruby​​ 中使用 .try 函数和 .map 函数 - 2

    我需要从json记录中获取一些值并像下面这样提取curr_json_doc['title']['genre'].map{|s|s['name']}.join(',')但对于某些记录,curr_json_doc['title']['genre']可以为空。所以我想对map和join()使用try函数。我试过如下curr_json_doc['title']['genre'].try(:map,{|s|s['name']}).try(:join,(','))但是没用。 最佳答案 你没有正确传递block。block被传递给参数括号外的方法

随机推荐