一、背景
在使用Hive的数据开发工作中,为了处理复杂的业务需求,经常要用到行转列或列转行的操作。为了节省以后处理这类工作的时间,提高工作效率,现将Hive行列互转的操作方法总结如下。
二、列转行
列转行,顾名思义,将原本某列中一行的数据拆分为多行,该操作会使得数据行数增多。我们采用的方法是使用LATERAL VIEW语法,配合explode函数(或其他UDTF)
2.1 对于ARRAY类型或MAP类型的字段
我们知道,Hive中是有可以直接将一行输出为多行的函数的(即UDTF),比如explode函数,文档如下:
- explode函数
我们举一个例子来说明这个函数的用法。
数据集如下:
hive (article)> SELECT *
> FROM test_liezhuanhang_01 AS p;
OK
p.name p.class p.province p.score p.info
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18}
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17}
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17}
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18}
Time taken: 1.014 seconds, Fetched: 4 row(s)
其中score字段为数组ARRAY
hive (article)> SELECT explode(score) AS score
> FROM test_liezhuanhang_01;
OK
score
99
86
100
97
77
80
77
89
90
80
76
79
Time taken: 0.291 seconds, Fetched: 12 row(s)
可以看到原本的4行数据按数组顺序拆成了12行。explode函数同样也可以用在MAP类型上,代码如下:
hive (article)> SELECT explode(info) AS (info_key,info_value)
> FROM test_liezhuanhang_01;
OK
info_key info_value
身高 177
体重 60
年龄 18
身高 180
体重 70
年龄 17
身高 170
体重 50
年龄 17
身高 185
体重 72
年龄 18
Time taken: 0.07 seconds, Fetched: 12 row(s)
但是这类函数有如下三个限制:
- SELECT 中不能存在其他列
- 比如
SELECT name,explode(score) AS score FROM test_liezhuanhang_01
是不允许的
- 比如
- 不能嵌套使用
- 比如
SELECT explode(explode(score)) AS score FROM test_liezhuanhang_01
是不允许的
- 比如
- 不能和GROUP BY / CLUSTER BY / DISTRIBUTE BY / SORT BY等一起使用
如果想要解除上面的限制,就要配合LATERAL VIEW一起使用,文档如下:
- LanguageManual LateralView
代码如下:
hive (article)> SELECT name
> ,class
> ,province
> ,score
> ,info
> ,score_01
> FROM test_liezhuanhang_01 LATERAL VIEW explode(score) table1 AS score_01;
OK
name class province score info score_01
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 99
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 86
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 100
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 97
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 77
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 80
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 77
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 89
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 90
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 80
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 76
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 79
Time taken: 0.451 seconds, Fetched: 12 row(s)
可以看到数据变成了12行,而最后一列正是score字段拆分后的结果。我们可以理解为,Hive先对字段score执行explode函数,再将执行后的结果与原表做笛卡尔积的join,最后形成一个虚拟表table1,虚拟表中包含score拆分后的字段score_01。
LATERAL VIEW可以嵌套使用
hive (article)> SELECT name
> ,class
> ,province
> ,score
> ,info
> ,score_01
> ,info_key
> ,info_value
> FROM test_liezhuanhang_01
> LATERAL VIEW explode(score) table1 AS score_01
> LATERAL VIEW explode(info) table2 AS info_key,info_value;
OK
name class province score info score_01 info_key info_value
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 99 身高 177
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 99 体重 60
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 99 年龄 18
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 86 身高 177
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 86 体重 60
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 86 年龄 18
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 100 身高 177
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 100 体重 60
小明 1 山东 [99,86,100] {"身高":177,"体重":60,"年龄":18} 100 年龄 18
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 97 身高 180
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 97 体重 70
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 97 年龄 17
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 77 身高 180
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 77 体重 70
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 77 年龄 17
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 80 身高 180
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 80 体重 70
小王 2 北京 [97,77,80] {"身高":180,"体重":70,"年龄":17} 80 年龄 17
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 77 身高 170
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 77 体重 50
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 77 年龄 17
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 89 身高 170
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 89 体重 50
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 89 年龄 17
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 90 身高 170
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 90 体重 50
小赵 2 广东 [77,89,90] {"身高":170,"体重":50,"年龄":17} 90 年龄 17
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 80 身高 185
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 80 体重 72
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 80 年龄 18
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 76 身高 185
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 76 体重 72
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 76 年龄 18
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 79 身高 185
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 79 体重 72
小明 1 山东 [80,76,79] {"身高":185,"体重":72,"年龄":18} 79 年龄 18
Time taken: 0.04 seconds, Fetched: 36 row(s)
最后三列即为我们想要的结果,后一个LATERAL VIEW可以看作在前一个LATERAL VIEW的结果上执行,于是原本的4行拆分成为12行,再拆分成为36行。
注意:LATERAL VIEW还可以配合其他UDTF一起使用,而不是只能与explode函数一起使用。
2.2 对于STRING类型的字段
在工作中,我们并不总是能够碰到ARRAY,MAP字段这样的理想情况,很多时候,字段是以STRING的形式出现,那么这时候我们怎么进行列转行呢?
数据集如下:
hive (article)> SELECT *
> FROM test_liezhuanhang_02 AS p;
OK
p.name p.class p.province p.score p.info
小明 1 山东 99,86,100 身高:177,体重:60,年龄:18
小王 2 北京 97,77,80 身高:180,体重:70,年龄:17
小赵 2 广东 77,89,90 身高:170,体重:50,年龄:17
小明 1 山东 80,76,79 身高:185,体重:72,年龄:18
Time taken: 0.073 seconds, Fetched: 4 row(s)
其中,score与info都是STRING类型,这时候我们可以采用split函数与str_to_map函数,将STRING类型转化为ARRAY和MAP类型。文档如下:
- split
- str_to_map
本例代码如下:
hive (article)> SELECT name
> ,class
> ,province
> ,split(score,',') AS score
> ,str_to_map(info,',',':') AS info
> FROM test_liezhuanhang_02;
OK
name class province score info
小明 1 山东 ["99","86","100"] {"身高":"177","体重":"60","年龄":"18"}
小王 2 北京 ["97","77","80"] {"身高":"180","体重":"70","年龄":"17"}
小赵 2 广东 ["77","89","90"] {"身高":"170","体重":"50","年龄":"17"}
小明 1 山东 ["80","76","79"] {"身高":"185","体重":"72","年龄":"18"}
接下来,我们就可以用上一节中的LATERAL VIEW语法,配合explode函数进行列转行啦。
三、行转列
行转列分为两种,一种是将某列中多行的数据合并为一行,另一种是将某列中的值转化为列名,我们分别来看。
3.1 将某列中多行的数据合并为一行
数据集如下:
hive (article)> SELECT *
> FROM test_hangzhuanlie_01 AS p;
OK
p.name p.province p.score
小明 山东 99
小明 山东 90
小明 江苏 97
小明 江苏 88
小王 山东 98
小王 山东 95
小王 江苏 89
小王 江苏 83
小王 山西 87
小王 山西 86
小赵 山东 77
小赵 山东 79
小赵 江苏 92
小赵 江苏 77
小赵 山西 69
小赵 山西 79
我们想要把名字和省份相同的score值合并到一起,用逗号分隔。应该怎么做呢?
可以使用concat_ws函数配合collect_list函数,文档如下:
- collect_list
- concat_ws
代码如下:
hive (article)> SELECT name
> ,province
> ,concat_ws(',',collect_list(score)) AS score
> FROM test_hangzhuanlie_01
> GROUP BY name
> ,province;
Total MapReduce CPU Time Spent: 6 seconds 430 msec
OK
name province score
小明 山东 99,90
小明 江苏 97,88
小王 山东 98,95
小王 山西 87,86
小王 江苏 89,83
小赵 山东 77,79
小赵 山西 69,79
小赵 江苏 92,77
如果是在Mysql中,我们可以直接使用group_concat函数代替上面两个函数的组合;如果是在Oracle中,我们可以用wm_concat。
3.2 将某列中的值转化为列名
数据集如下:
hive (article)> SELECT *
> FROM test_hangzhuanlie_02 AS p;
OK
p.name p.province p.score
小明 山东 99,90
小明 江苏 97,88
小王 山东 98,95
小王 山西 87,86
小王 江苏 89,83
小赵 山东 77,79
小赵 山西 69,79
小赵 江苏 92,77
没错,这里偷个懒,直接拿上一节的结果数据作为这一节的数据集。
这次我们想将省份名作为列值,即第一列为这个同学在山东得到的分数,第二列为这个同学在江苏得到的分数,等等。实现方式如下:
hive (article)> SELECT name
> ,max(CASE WHEN province='山东' THEN score ELSE NULL END) AS shandong_score
> ,max(CASE WHEN province='江苏' THEN score ELSE NULL END) AS jiangsu_score
> ,max(CASE WHEN province='山西' THEN score ELSE NULL END) AS shanxi_score
> FROM test_hangzhuanlie_02
> GROUP BY name;
Total MapReduce CPU Time Spent: 5 seconds 230 msec
OK
name shandong_score jiangsu_score shanxi_score
小明 99,90 97,88 NULL
小王 98,95 89,83 87,86
小赵 77,79 92,77 69,79
Time taken: 17.584 seconds, Fetched: 3 row(s)
搞定收工!