一、问题
我们经过遇到这样的问题,要补全一个表某列的空值,策略是按某个规则排序后,取上一个非空的值替代。
二、思路
1、首先分组获取行号
2、找到被替代值所在的行号,以及下一个替代值的行号
3、把开始行号到下一个替代值行号的区间(左闭右开区间)数组展开,得到被替代值在每个行的值
4、原表join3生成的表
三、实现
1、先做一个测试表,有三列字段:uid,time,event。分别代表用户在什么时候发生什么事件。
数据如下:
uid time event
1 2018-12-02 11:00:29 A
1 2018-12-02 11:00:30 ""
1 2018-12-02 11:00:31 B
1 2018-12-02 11:00:32 ""
1 2018-12-02 11:00:33 ""
2 2018-12-02 11:00:40 B
2 2018-12-02 11:00:41 ""
2 2018-12-02 11:00:42 C
2 2018-12-02 11:00:44 ""
现在想按照前面非空的值补全空值,即把事件列的空值补成 A B C
2、直接上代码
--udf
add jar udf-1.1.jar;
create temporary function get_range as 'com.hive.udf.Range';
with all_data as (
select
a.*
--对每个用户分组获取记录排序
,row_number() over (partition by uid order by time asc) as rank
--对每个用户分组获取总记录数
,count(1) over (partition by uid) as cnt
from test_table a
)
select
a.uid
,a.time
,b.event
from all_data a
left join (
select
uid,event,rank
from (
select
uid
,event
,rank as start_rank
--获取每个用户下一个不为空的event所在行数,最后一行获取到rank为null,故用cnt+1代替
,cast(coalesce(lead(rank) over(partition by uid order by rank asc),cnt+1) as int) as next_rank
from all_data
--过滤出event不为空的记录
where event is not null and event<>''
) a
--展开数组
lateral view explode(get_range(start_rank,next_rank)) b as rank
) b
on a.uid = b.uid
and a.rank = b.rank
3、上述过程使用了一个udf,类似python的range方法,返回一个数据范围的数组
package com.hive.udf;
import org.apache.hadoop.hive.ql.exec.UDF;
import java.util.ArrayList;
public class Range extends UDF {
public ArrayList<Integer> evaluate(Integer startNum, Integer endNum){
if(startNum == null || endNum == null || startNum > endNum){
return null;
}
ArrayList<Integer> list = new ArrayList<Integer>();
for(int i = startNum; i < endNum; i++){
list.add(i);
}
return list;
}
}
4、输出结果如下
uid time event
1 2018-12-02 11:00:29 A
1 2018-12-02 11:00:30 A
1 2018-12-02 11:00:31 B
1 2018-12-02 11:00:32 B
1 2018-12-02 11:00:33 B
2 2018-12-02 11:00:40 B
2 2018-12-02 11:00:41 B
2 2018-12-02 11:00:42 C
2 2018-12-02 11:00:44 C