在实际使用Hive的过程中,常常会涉及到行列转换,细分的话,有下面4种类型的行列转换,分别是:
下面我们通过样例介绍每种行列转换的实现方法。
样例表
班级成绩表:
姓名(name) 学科(subject) 成绩(score)
A 语文 70
A 数学 90
A 英语 80
B 数学 95
B 英语 85
B 语文 75
行列转换思路分析及实现
行转多列
如果需要将上面的样例表转换为
“姓名 | 语文成绩 | 数学成绩 | 英语成绩”
这样的格式,那么这就需要用到行转多列。
思路:
涉及到行转成列,肯定是会按照某一列或者某几列的值进行分组来压缩行数,所以会用到group by。
分组之后需要用到聚合函数,由于多列中的每列只关心自己对应的数据,所以要使用case语句进行选择,至于聚合函数,只要数据能保证唯一性,max、min、avg(数值类型)等都可以
样例SQL:
select name,
max(case when subject = '数学' then score else null end) math,
max(case when subject='英语' then score else null end) english,
max(case when subject='语文' then score else null end) chinese
from t1
group by name;
多列转行
将上面行转多列的结果再转回成原始表结构的过程,就是多列转行
思路
列转行,会涉及到行数的增加,所以会用到UDTF,而UDTF只是针对某一列的,要把这列扩展后生成的多行数据和源表中的各列拼接在一起,需要用到lateral view语法;
需要将多列里各列的列名(业务含义),在新数据中当做一个标识列,而与lateral view联合使用的explode函数是支持Map类型的,所以要先将原表里的多列变换成Map类型的一列,然后再用lateral view拆开。
样例SQL:
select name,subject,score from
(
select name,map('数学',math,'英语',english,'语文',chinese) scores
from t2
) tt1
lateral view explode(scores) tt2 as subject,score;
行转单列
将原始表转换为
“姓名 | 所有科目成绩集合 ”
则涉及到行转单列
思路
和行转多列一样,行数会减少,所以需要用到group by,然后转成的是单列,所以需要用到collect_list或者collect_set聚合函数,如果字段类型想要是有分隔符隔开的字符串,再套上一层concat_ws;
上面的方案得出的array或者字符串是乱序的,如果想要进行排序,可以使用sort_array函数。但sort_array只能按字段类型的升序排列(数值字段自然序,字符串字段字典序);
如果想要自己指定排序规则,或者排序的不是单列里的这个字段(比如班级考试成绩表,所有分数字段里需要根据学科进行排序,而不是分数高低),则需要使用collect_list加上over子句来实现。
样例SQL:
1、成绩不排序
select name,concat_ws(',',collect_list(score))
from t1
group by name;
2、按分数高低排序
select name,concat_ws(',',sort_array(collect_list(score)))
from t1
group by name;
3、按学科进行排序
select name,concat_ws(',',max(a)) from
(
select name,
collect_list(score) over(partition by name order by subject
rows between unbounded preceding and unbounded following) a
from t1
) tt1 group by name;
4、按指定的学科顺序排序
select name,concat_ws(',',max(a)) scores from
(
select name,
collect_list(score) over(partition by name
order by case when subject='语文' then 1
when subject='数学' then 2
when subject='英语' then 3 end
rows between unbounded preceding and unbounded following) a
from t1
) tt1 group by name;
单列转行
将上面的结果转成原始表结构,就是单列转行
思路
和多列转行一样,使用lateral view加explode来转换。但这种方式转换出来会丢失掉科目字段信息;
如果需要加上科目信息,则需要按照单列里面的顺序的业务含义,先将单列转成Map类型,将科目加到数据里,然后再使用lateral view转换。
样例SQL:
1、无科目字段
select name,score from
(
select name,split(scores,',') s from t3
) tt1
lateral view explode(s) tt2 as score;
2、有科目字段
select name,subject,score from
(
select name,map('语文',s[0],'数学',s[1],'英语',s[2]) ss from
(
select name,split(scores,',') s from t3
) tt1
) ttt1
lateral view explode(ss) ttt2 as subject,score;