我将其他相关不必要的细节隐去,只保留最终的技术点,希望能对以后有需要的朋友提供些许帮助。
最近在进行hive表的统计的时候有这样的一个需求,
hive中增量统计
一 、任务需求:
有这样的两个表 table0,table01,
table0为原始数据表:
age | num | total | pday |
---|---|---|---|
50 | 20 | 100 | 20190420 |
60 | 20 | 100 | 20190420 |
70 | 20 | 100 | 20190420 |
80 | 20 | 100 | 20190420 |
age为年龄,50后 60后 70后 80后
num为在20190420这一天新增的人数
total 为截至到20190420这一天的所有人数
还有一张table1表,长这个样子
age | num | pday |
---|---|---|
50 | 10 | 20190421 |
60 | 10 | 20190421 |
70 | 10 | 20190421 |
80 | 10 | 20190421 |
很容易理解,年龄为50后的人 今天又新增了10人,年龄为60后的人今天也新增了10人,以此类推
需求是,需要统计在4月21日的总人数,生成像table0那样的表格,也就是以下这张表,我称为结果表 result_table
age | num | total | pday |
---|---|---|---|
50 | 10 | 110 | 20190421 |
60 | 10 | 110 | 20190421 |
70 | 10 | 110 | 20190421 |
80 | 10 | 110 | 20190421 |
我们以50后为例,表中num表示的是今日新增10人,总人数变成了昨天的100加上今天的10人,共110人。
sql语句如下,比较简单
select
t0.age,
t1.num,
t1.num+t0.total as total
from
(select age,num,total from table0 where pday=20190420) as t0
join
(select age ,num from table1 where pday=20190421) as t1
on
t0.age=t1.age
二、出现的问题
1 很容易产生的问题如下:在未来的某一天,新增的人只有50后 60后,没有80 90后,那table0与table1进行join的结果就只能有50后60后, 80和90的就会消失,大家想想是不是这个道理。如下表,就是在4月25日新增的人只有50和60的人,那么80 90的数据就凭空消失了,这断然不是我们想要的结果,我们想要的结果是,即使在20190425这一天没有新增80 90后的人,那也应该继承自上一天的数据,不应该因为没有join到就砍掉这部分人。
2 再比如,若未来某一天新增的人除了50 60 70 80 的人,还新增了90后的人,怎么办?新增的90后在table0中是查不到总人数的,那么t0.total就是取不到数据的,理论上很容易想到,应该把今天新增的90后的人数作为总人数,以下两个表为问题示意图,没有理解的朋友希望仔细思考一下。
总结起来就是 左右表不能完全join起来。该怎么办
age | num | total | pday |
---|---|---|---|
50 | 30 | 140 | 20190422 |
60 | 30 | 140 | 20190422 |
… | … | … | … |
age | num | total | pday |
---|---|---|---|
50 | 30 | 140 | 20190422 |
60 | 30 | 140 | 20190425 |
70 | 30 | 140 | 20190425 |
80 | 30 | 140 | 20190425 |
90 | ? | ? | 20190425 |
三 解决方案
我们需求的本意是想要左表中,右表中age都要保留,无论是否能连接起来。所以此处应该使用full join。
在hive中是支持full join的,也就是左右表中年龄存在的,都应该保留起来,这显然可以解决此问题。
可是直接将sql0中的语句中join变为full join是不行的,还要考虑在左表中查不到数据,或者说在右表中查不到数据,该如何进行相加?
这时候问题就比较复杂了,我们重新回顾一下这两张表
table0
age | num | total | pday |
---|---|---|---|
50 | 20 | 100 | 20190420 |
60 | 20 | 100 | 20190420 |
70 | 20 | 100 | 20190420 |
80 | 20 | 100 | 20190420 |
table1
age | num | pday |
---|---|---|
60 | 10 | 20190421 |
70 | 10 | 20190421 |
80 | 10 | 20190421 |
90 | 10 | 20190421 |
请注意此数据和之前简单版数据的区别:
table0表中含有50 60 70 80 ,table1表中含有60 70 80 90
首先我们来看看执行full join之后表是什么样子,sql:
select * from
table0
full join
table1
on table0.age=table1.age
age | num | total | pday | age | num | pday |
---|---|---|---|---|---|---|
60 | 10 | 110 | 20190420 | 60 | 10 | 20190421 |
70 | 10 | 110 | 20190420 | 70 | 10 | 20190421 |
80 | 10 | 110 | 20190420 | 80 | 10 | 20190421 |
50 | 10 | 110 | 20190420 | |||
90 | 10 | 20190421 |
接下来就是将左右表中age和age进行组合 ,左右表中num 和num组合,total生成新的total
我们先理清逻辑,很明显,左表中age为null就要显示右表中的age,左表中num为null 就要使用右表中num
至于total比较复杂,
第一种情况,左表中有total,右表中有num,那生成的total 就是两者相加
第二种 左表中有total,右表中无num,那新生成的total 就是原来的total
第三种 左表中无 total,右表中有num,那新生成的total 就是num
第四种 左表中无total,右边中无num,那新生成的total 就是null 我们可以默认为0(数量)
理清了逻辑就是看使用哪些方法来实现功能了。
首先
我们可以使用case when 函数
case when t0.age is null then t1.age else t0.age end
或者可以使用coalesce函数
coalesce(value1,value2 ,…)
value1为null 就显示value2的值,以此类推,具体使用方法在此就不说明了。
我们在此使用case when。
因为total的处理比较复杂,使用coalesce满足不了需求
下面贴出来我再hive里进行测试的代码(mysql不支持full join 我有没有oracle数据库 只能使用hive了 大哭)
1 创建表格
create table table0(
age int comment 'age',
num int comment '数量',
total int comment '总数'
)
partitioned by (pday int)
row format delimited
fields terminated by '\t'
lines terminated by '\n'
stored as textfile;
------------------------
create table table1(
age int comment 'age',
num int comment '数量'
)
partitioned by (pday int)
row format delimited
fields terminated by '\t'
lines terminated by '\n'
stored as textfile;
2 准备文本文件
50 20 100 20190420
60 20 100 20190420
70 20 100 20190420
80 20 100 20190420
60 10 20190421
70 10 20190421
80 10 20190421
90 10 20190421
3 加载到hive表中
hive (default)> load data local inpath "/home/hadoop/myself_shell/test0"
into table table0 partition(pday=20190420);
hive (default)> load data local inpath "/home/hadoop/myself_shell/test1"
into table table1 partition(pday=20190421);
4 运行hive 执行sql语句,以下是完整的sql语句
select
case when t1.age is null then t0.age else t1.age end as age,
case when t1.num is null then t0.num else t1.num end as num,
case when t1.num is null and t0.total is null then 0
when t1.num is null and t0.total is not null then t0.total
when t1.num is not null and t0.total is null then t1.num
when t1.num is not null and t0.total is not null then t1.num+t0.total
else 0
end as total
from
(select age,num,total from table0 where pday=20190420) as t0
full join
(select age ,num from table1 where pday=20190421) as t1
on
t0.age=t1.age
5 运行结果
age | num | total | pday |
---|---|---|---|
50 | 20 | 100 | 20190421 |
60 | 10 | 110 | 20190421 |
70 | 10 | 110 | 20190421 |
80 | 10 | 110 | 20190421 |
90 | 10 | 10 | 20190421 |