基于Hive SQL 提取加工数据是每个数据分析师的工作日常,但屏幕面前的你是否遇到过这样的囧境:数仓表中的某个字段并非是以往那种一行一个实体信息的结构化数据,而是 json格式的半结构化数据。
如果SQL高级函数掌握得不够熟练,那么面对这种存储排列方式极为复杂的json数据必然会显得手足无措。所以今天让我们带大家仔细研究一下 json 数据的结构,同时给到大家解析json的思路和模版,以快速地解析 json 数据。
一、json数据格式有哪些?
json对象:json = { “name”: “张三”, “sex”: “25” };
json数组:json = [ { “name”: “张三”, “sex”: “25” },{ “name”: “王五”, “sex”: “18” }]
这里,{} 双括号表示对象; [] 中括号表示数组; “” 双引号内是属性或值; : 冒号表示后者是前者的值 (注意:这个值可以是字符串、数字、也可以是另一个数组或对象)
对于复杂的json数据,其属性对应的值往往不是单纯的字符串或数字,而是一个数组或对象,这给json解析增加更大的难度。
比如,下面的例子中,json_a 中 tags 属性对应的值为一个数组; 而 json_b 中的data对应的 属性的值为一个对象,该对象中dataInfo对应的值为字符串,但该字符串又是由一个带双引号的json组成。
json_a= {"userId": "张三", "tags": [{"tag": "男"} ,{"tag": "高收入","value":"2w+"}]}
json_b={"userId": "张三","data":{"dataInfo":"{\"5fsfa\":\"successed\",\"fdt57\":\"Error\"}","id":"K499z"},"result":"ok"}
小tips:
如果想快速了解某json是否存在互相嵌套的关系,可以使用json网页工具进行结构识别(
https://www.sojson.com/simple_json.html)
json_a 的存储格式更为常见些,因此后续会以json_a为例给大家讲解解析json需要用到的函数。
二、解析json需要用到哪些函数?
由于 json_a 的排列格式更为常见些,因此后续会以json_a为例给大家讲解解析json需要用到的函数。json_a 的格式如下:
json_a= {"userId": "张三", "tags": [{"tag": "男"} ,{"tag": "高收入","value":"2w+"}]}
1、get_json_object
>> select get_json_object (json_a,'$.userId')
>> 张三
举例2:读取json数组
>> select get_json_object (json_a,'$.tags[1]')
>> {"tag":"高收入","value":"2w+"}
2、json_tuple
用途:
比json_tuple更强大,用来一次性解析json字符串中的多个字段
用法:
函数第一个参数填写json变量,后面参数填写 key 的名称
举例:
>> select json_tuple (json_a,'userId','tags') --/** 输出两列数据**/--
>> 张三 {"tag":"高收入","value":"2w+"}
值得注意的是,如果要把json_a 中的tag 按照行输出,则以上两个函数都显得无能为力,需要用到以下的函数。
3、explode
>> ["北京","上海","天津","杭州"]
map 格式举例:
>> {"语文":60,"数学":80,"英语":99}
struct 格式举例:
>> {"course":"english","score":80}
{"course":"math","score":89}
{"course":"chinese","score":95}
举例说明 explode()的用法
>> select explode(array('A','B','C'));
>> select explode(map('A',10,'B',20,'C',30))
C 30
4、LATERAL VIEW explode和LATERAL VIEW json_tuple
SELECT * FROM exampleTableLATERAL VIEW explode(col1) myTable1 AS myCol1 LATERAL VIEW explode(myCol1) myTable2 AS myCol2 LATERAL VIEW json_tuple(myCol2,'key_1','key_2') myTable3 AS myCol3_value1,myCol3_value2
6、regexp_replace 和 regexp_extract 以及正则匹配表达式
值得注意的是,假设我们要将
json_a
中的tag按列输出,但由于
explode()
函数的输入只能是
map
或
array
,如果直接将tags的json数组作为输入,系统会报错。
正确的方法是,将tags的json数组两边的中括号去掉,然后按照一定规则进行分列,以转换为map格式。因此,需要用到下面的正则函数以及分隔split函数。
1) 正则表达式大全
参考以下网址: https://www.jb51.net/article/97732.htm
2) regexp_replace
>> regexp_replace (json_a,'\\[|\\]','')
3) regexp_extract
>> regexp_extract(tags,'^\\[(.+)\\]$',1)
7、split
SELECT SPLIT('http://facebook.com/path1/p.php?k1=v1&k2=v2#Ref1','\?') [0] AS A,
--对url进行使用?进行切割,返回值是数组,这里取?前面的值。
三、经典实战案例
1、案例 A
需求背景: hive表中某字段tag按行存储了每个用户的多个标签,但如果想要计算每个标签下的用户数,需要将tag里的userID、tag、weight字段信息扩展抽取出来。
字段tag的数据取值如下:
{"userId": "张三", "tags": [{"tag": "男"} ,{"tag": "高收入","value":"2w+"}]}
{"userId": "李四", "tags": [{"tag": "女"} ,{"tag": "中等收入","value":"1w+"}]}
思路整理:
以下为可在 Hive 环境里执行的代码:
with temp as (
select
'{"userId": "张三", "tags": [{"tag": "男"} ,{"tag": "高收入","value":"2w+"}]}' as json
union
select
'{"userId": "李四", "tags": [{"tag": "女"} ,{"tag": "中等收入","value":"1w+"}]}' as json
) -- 将数据源创建为临时表,字段名为json,仅包含两行数据
-- 具体的json解析过程
select
distinct t.userId,
table_tag_data.tag,
table_tag_data.weight
select
get_json_object(temp.json, '$.userId') as userId,
get_json_object(temp.json, '$.tags') as tags
from temp
) t LATERAL VIEW explode(
split(
regexp_replace(
regexp_replace(tags, '\\[|\\]', ''),
-- 将 json 数组两边的中括号去掉
'\\}\\,\\{', -- 只把json对象之间之间的逗号换成分号,注意要避免把map内部的有用逗号也去掉
'\\}\\;\\{'
'\\;' -- 按照分号分割
) table_tags as tag_data LATERAL VIEW json_tuple(table_tags.tag_data, 'tag', 'value') table_tag_data as tag,
weight
小tips: 上面案例的数据格式是json数据的常见格式,后续重复遇到与之高度类似的json结构概率极大,到时可以直接套用上述中的代码思路进行快速解析,因此建议收藏以上代码。
2、案例 B(难度升级)
{"userId": "张三","data":{"dataInfo":"{"5fsfa":"successed","fdt57":"Error"}","id":"K499z"}}
{"userId": "王五","data":{"dataInfo":"{"2345e":"successed"}","id":"K499z"}}
...
思路整理:
with temp_db as (
select
'{"userId": "张三","data":{"dataInfo":"{\"5fsfa\":\"successed\",\"fdt57\":\"Error\"}","id":"K499z"}}' as json
union
select
'{"userId": "王五","data":{"dataInfo":"{\"2345e\":\"successed\"}","id":"K499z"}}' as json
), -- 将数据源创建为临时表,字段名为json,仅包含两行数据
temp as (
select
regexp_replace(regexp_replace(json, '\\"\\{', '{'), '\\}\\"', '}') as json
temp_db
) -- 将json字段中dataInfo的值的的双引号去掉
select
userId,
regexp_extract(dataInfo_exp, '^(.+)\\:', 1) AS key, -- 提取冒号前的key
regexp_extract(dataInfo_exp, '\\:(.+)$', 1) AS value, -- 提取冒号后的value
select
get_json_object(temp.json, '$.userId') AS userId,
get_json_object(temp.json, '$.data') AS data,
get_json_object(temp.json, '$.data.dataInfo') AS dataInfo,
get_json_object(temp.json, '$.data.id') AS id
) t1 lateral view explode(
split(
regexp_replace(
regexp_extract(t1.dataInfo, '^\\{(.+)\\}$', 1), -- 去掉dataInfo的json对象{}双括号
'\\"',''
), -- 去掉双引号使之成为一个普通字符串
',' -- 按照逗号分割
) t2 as dataInfo_exp;
以上便是全部内容啦。相信大家在阅读完本文后,如果再遇到复杂的json解析问题,至少可以做到不再焦灼了,可以直接套用以上的解析模版和思路进行解析。