首页 / 资讯中心 / 文章详情

Hive复杂数据类型实战:Array、Map、Struct建表与查询优化

Hive复杂数据类型实战:Array、Map、Struct建表与查询优化 ★ FEATURED ARTICLE
做过数据仓库的朋友应该都有过这种经历上游给过来的埋点日志或者订单明细某个字段里塞着一长串花括号和方括号套起来的内容看一眼就头大。不少人第一反应是找正则硬拆或者干脆把整个字段当成JSON字符串到处get_json_object绕了很远的路。其实Hive早就给了一套原生的复杂数据类型工具也就是Array、Map、Struct。这套东西一旦真正吃透处理埋点事件流、用户标签、订单商品明细这些嵌套结构会顺手非常多。这篇文章我就围绕Hive复杂数据类型把Array、Map、Struct三种类型从建表加载、日常查询、嵌套组合到性能优化和踩坑经验完整过一遍。适合刚接触Hive不久、被复杂类型卡住的同学也适合已经在用但总在嵌套展开和分隔符上出问题的开发。下面讲的都是我实际跑过的场景和SQL可以直接拿去用。1. 先搞懂Array、Map、Struct到底解决什么问题1.1 三种类型用一个生活化类比理解不少人对复杂类型的理解停留在“语法会用”的层面但遇到真实业务时却不知道怎么选。这里先给一个非常直观的类比。假设你要在Excel里记录一个人的信息一个人有多个手机号这就是一个Array元素类型相同个数不固定。一个人有一堆属性每个属性都有一个名字和值比如“城市上海”“等级VIP”这就是一个Map一个键对应一个值。一个人本身包含多个固定字段比如姓名、年龄、住址这些字段合在一起作为一个整体出现这就是一个Struct。Array是“同类型元素的集合”Map是“键值对的集合”Struct是“不同类型字段的复合体”。三者可以互相嵌套比如Array里装StructStruct里装Map这正是真实业务中最常见的形态。1.2 业务场景里为什么必须用它们我在实际中遇到最典型的场景有三个。第一个是埋点日志。一个用户访问一次页面可能触发多个事件每个事件又有事件ID、商品ID、时间戳和扩展属性。如果按关系建模需要拆成事件明细表和扩展属性表再用join关联。但用复杂类型可以直接建模成一行一用户事件字段用Array 扩展属性用Map保留原始粒度查询时一条SQL就能展开。第二个是用户标签系统。用户的标签数量不固定标签名和标签值天然是键值对直接用一个MapString, String存比建一张用户标签关联表要省事得多。尤其是标签经常上线下线表结构完全不用动。第三个是订单商品明细。一个订单包含多个商品每个商品有SKU、数量、单价。如果下游分析经常以订单维度去聚合把商品列表建模成Array 就比拆成订单明细表再join回订单主表更直观。复杂类型的核心价值其实就是“把一对多的关系内联到一行”。它和关系型范式完全相反但在大数据分析场景里它减少了大表之间的join也更贴合上游JSON、日志这种自带嵌套结构的原始数据。1.3 什么时候不该用复杂类型复杂类型虽然好用但并不是所有场景都适合。如果你的下游是一个固定不变的报表需要按商品维度做各种汇总那么订单里面的商品拆成明细表反而更合适。另一个不适合的场景是某个复杂类型字段需要频繁参与join条件例如直接用Map的某个键去关联维度表这种写法在Hive里别扭且性能差不如提前把键值对展开成普通字段。我的经验是一个字段到底建模成复杂类型还是拆开取决于“你的分析主体是谁”。分析主体是用户或订单就把一对多内容内联进去分析主体是商品或事件本身就应该拆出来。建模阶段多花十分钟想清楚这件事后面少踩无数坑。2. 建表与数据加载第一道坎全在SerDe分隔符2.1 典型DDL写法先给一张实战中非常常见的表结构我们后面的例子都跑在它上面。CREATE TABLE user_event_log ( uid STRING, event_list ARRAYSTRUCTevent_id:STRING, sku:STRING, ts:BIGINT, attr_map MAPSTRING, STRING, base_info STRUCTnickname:STRING, level:INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY | COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMINATED BY : STORED AS TEXTFILE;需不需要先解释一下三个“TERMINATED BY”它们分别控制不同层级的分隔符FIELDS控制列的边界COLLECTION ITEMS控制数组元素和Struct字段的边界MAP KEYS控制键值对之间和键与值之间的边界。有一点特别容易被忽略Struct内部的字段分隔符用的是COLLECTION ITEMS TERMINATED BY和Array元素用的是同一个。很多人第一次建表时以为Struct字段也应该用FIELDS分隔符结果加载之后所有Struct字段解析成一坨字段串位。2.2 数据文件如何组织针对上面的表一行文本大致长这样u01|view_item:sku_001:1721000000,add_cart:sku_002:1721000005|category:book,city:shanghai|alice:3我们来拆解一下。|分隔四个列。第二列的event_list是Array 用,分隔两个事件每个事件内部再用:分隔Struct的三个字段所以是view_item:sku_001:1721000000。第三列是Map用,分隔两个键值对再用:分隔键与值。第四列是Struct内部也用:分隔字段所以是alice:3。把数据保存成文件后用常规的LOAD就能导入。LOAD DATA LOCAL INPATH /data/user_event_log.txt INTO TABLE user_event_log;2.3 分隔符与序列化器的关系这里想多说几句原理因为理解了就不会总在解析上翻车。Hive默认使用LazySimpleSerDe做文本解析它按照你指定的分隔符把一行文本逐层切分。切分顺序是先用行内字段分隔符分出列再对复杂类型字段用集合分隔符分出元素再用键值分隔符分出Map内容。这个“逐层切分”的机制决定了两个约束。第一分隔符层级之间不能互相冲突比如Map的键值分隔符如果和集合分隔符一样那么整个Map的解析会完全乱掉因为分完键值之后下一层又会把键值对的边界切一遍。第二嵌套层数越多文本格式越难表达。例如MapString, ArrayString这类结构外层的键值分隔和内层的数组元素分隔如果配置不当数据根本分不出来。在实际生产环境中我的建议是建表练手用TEXTFILE没问题但一旦数据结构里出现两层以上的嵌套就不要再用LazySimpleSerDe的默认文本方式了直接换成ORC或Parquet存储复杂类型的序列化交给列式存储自己处理省心非常多。这一点在后面性能部分还会再提。2.4 用STACK造数据快速练手很多初学者卡在没有合适数据文件。其实Hive自带一个STACK函数非常适合在练习时临时构造行数据语法是STACK(行数, 第1行字段..., 第2行字段..., ...)。SELECT uid, event_list, attr_map, base_info FROM ( SELECT STACK( 2, u01, array(named_struct(event_id, view_item, sku, sku_001, ts, 1721000000), named_struct(event_id, add_cart, sku, sku_002, ts, 1721000005)), map(category, book, city, shanghai), named_struct(nickname, alice, level, 3), u02, array(named_struct(event_id, view_item, sku, sku_099, ts, 1721000100)), map(category, electronics, city, beijing), named_struct(nickname, bob, level, 5) ) AS (uid, event_list, attr_map, base_info) ) tmp;这个写法不需要建表、不需要造文件直接在同一个会话里生成测试数据所有下面几章的SQL都可以基于它调试。强烈建议你把这段存成一个公共视图或者常用SQL片段排查问题时会快很多。3. Array操作实战展开、过滤与聚合3.1 最基本的信息获取先掌握数组的几个基础操作它们是后续所有复杂操作的地基。-- 数组长度 SELECT uid, SIZE(event_list) AS event_cnt FROM user_event_log; -- 某数组是否包含指定元素 SELECT uid, ARRAY_CONTAINS(event_list, view_item) FROM user_event_log;这里有几个我踩过的点SIZE()对NULL就返回NULL对空数组返回0所以统计时要留意NULL和0的差异别直接当成0处理。ARRAY_CONTAINS只能判断元素值本身如果数组元素是Struct你直接用ARRAY_CONTAINS(event_list, view_item)是不行的因为数组里根本不是字符串类型类型都对不上。这种情况要用后面讲的高阶函数FILTER或者先展开再判断。3.2 explode lateral view 行展开数组最强大的操作是展开成多行。很多新手分开都能看明白但组合起来就晕。我建议这样理解explode负责把数组的每个元素变成一行lateral view负责把展开的结果拼回原表并且每个原始行会因为展开而复制成多份。SELECT uid, e.event_id, e.sku, e.ts FROM user_event_log LATERAL VIEW EXPLODE(event_list) t AS e;注意t AS e里面的e是整个Struct元素访问Element内部的字段要用e.event_id而不是直接event_id。这是非常高频的报错点。如果只想保留某个事件类型最直接的思路是先展开再过滤SELECT uid, e.sku FROM user_event_log LATERAL VIEW EXPLODE(event_list) t AS e WHERE e.event_id add_cart;这个写法简单但它会先把所有事件都展开再在查询层做过滤。数据量大的时候有个性能隐患后面我会专门讲怎么优化。3.3 collect_list / collect_set 聚回数组展开之后你经常需要把多行聚回一个数组。比如把每个用户触发的SKU汇总成一个数组SELECT uid, COLLECT_LIST(sku) AS sku_list FROM ( SELECT uid, e.sku FROM user_event_log LATERAL VIEW EXPLODE(event_list) t AS e ) t GROUP BY uid;这里有一个很重要的注意点COLLECT_LIST保留重复元素顺序并不保证COLLECT_SET去重但同样不保证顺序。如果你需要排序后的数组有两个办法要么用SORT_ARRAY(COLLECT_SET(sku))要么在聚合成数组前先对子查询结果排序。实测中第一种更简单但如果元素是StructSORT_ARRAY就不好用了需要先排好再聚合。3.4 数组去重、排序与截取细节数组的进阶处理还有几个实用技巧。数组去重除了COLLECT_SET如果是在内存中操作一个已有数组Hive没有直接提供类似DISTINCT的数组函数所以需要借助展开再聚合SELECT uid, COLLECT_SET(x) AS dedup_arr FROM user_event_log LATERAL VIEW EXPLODE(some_arr) t AS x GROUP BY uid;数组截取可以用SLICE(arr, start, length)下标从0开始。数组的顺序默认不是稳定的如果你需要稳定排序用SORT_ARRAY最省事。还有一个反直觉的细节SORT_ARRAY只支持元素可比较的类型比如Int、String。如果数组元素是Map或Struct这个函数直接报错。遇到这种情况要么先展开再按字段排序聚合要么检查是不是数据结构设计过度。4. Map操作实战键值世界的读写和清洗4.1 键值访问的常用手段Map的日常操作和数组不太一样它的核心是用键取值。-- 取单个键的值 SELECT uid, attr_map[category] AS category FROM user_event_log; -- 取所有键 SELECT uid, MAP_KEYS(attr_map) AS keys FROM user_event_log; -- 取所有值 SELECT uid, MAP_VALUES(attr_map) AS values FROM user_event_log; -- 键值对数量 SELECT uid, SIZE(attr_map) AS tag_cnt FROM user_event_log;一个比较坑的地方用attr_map[category]取键时如果这个键不存在返回NULL。问题在于如果某个键的值本身就是NULL你也分不清到底是“键不存在”还是“值为NULL”。所以做业务统计时建议用IF(attr_map[category] IS NULL, 0, 1)之类的逻辑去兜底。4.2 str_to_map从字符串到Map实际工作中很多Map最初是从埋点URL的query_string或者日志里的键值字符串解析来的。Hive提供了STR_TO_MAP函数一行SQL就能完成转换。SELECT STR_TO_MAP(cityshanghaicategorybookfromhome, , ) AS parsed_map;这个函数的三个参数分别是原始字符串、键值对之间的分隔符、键和值之间的分隔符。这个场景在日志解析中极其常见。以前用get_json_object一层层解现在一条STR_TO_MAP就能转成一个标准Map字段。要提醒的是原始字符串里如果出现了分隔符本身解析会出问题。所以在上游日志格式允许的情况下尽量统一使用一些不太会出现在内容里的分隔符比如^或者控制字符。4.3 展开Map并过滤键值Map也可以像数组一样展开成两列一列是键一列是值SELECT uid, k, v FROM user_event_log LATERAL VIEW EXPLODE(attr_map) t AS k, v;展开之后最常做的操作是过滤。比如想找出所有值为1的标签SELECT uid, k, v FROM ( SELECT uid, k, v FROM user_event_log LATERAL VIEW EXPLODE(attr_map) t AS k, v ) tmp WHERE v 1;这里我特别想强调展开之后的别名k和v不能直接写到同一个SELECT外面的WHERE里。你必须套一层子查询否则Hive在解析时就找不到这两个列。这是Map展开场景下最频繁出现的报错。4.4 对Map做统计分析展开后的Map其实就是一张标准的键值明细表几乎所有分析都能做。统计每个用户标签的具体分布SELECT k, COUNT(*) AS cnt FROM user_event_log LATERAL VIEW EXPLODE(attr_map) t AS k, v GROUP BY k;统计每个用户拥有多少个值为1的标签SELECT uid, SUM(IF(v 1, 1, 0)) AS active_tag_cnt FROM user_event_log LATERAL VIEW EXPLODE(attr_map) t AS k, v GROUP BY uid;在做这类统计时我习惯把“展开成键值明细”这一步提前放到一个公共视图里。一方面代码复用率高另一方面可以让负责过滤和聚合的上游逻辑更清晰。实际排错时你会发现把每一步拆分出来打印中间结果比写一条巨型SQL再debug要高效得多。5. Struct操作实战结构体访问与构造5.1 访问Struct字段的两种姿势Struct和前面两种类型最大的区别是它的字段是固定的、有名字的。访问方式也最直接用点号就行。SELECT base_info.nickname, base_info.level FROM user_event_log;如果Struct嵌在Array里面比如event_list这个数组每个元素都是一个Struct访问时先取出数组元素再点字段SELECT e.event_id, e.sku FROM user_event_log LATERAL VIEW EXPLODE(event_list) t AS e;还可以用下标取出数组中的第某个Struct再点字段SELECT event_list[0].event_id, event_list[0].sku FROM user_event_log;这个写法适合“每个用户只看第一个事件”的场景可以避免无谓的EXPLODE性能上更优。5.2 named_struct构造临时结构NAMED_STRUCT是构造Struct的工具常用于你希望在SQL执行过程中临时把几个字段打包成一个复合值。比如你想把用户的基础信息和统计信息组合成一个字段输出SELECT uid, NAMED_STRUCT( nickname, base_info.nickname, level, base_info.level, order_cnt, 0 ) AS user_info FROM user_event_log;这种做法的好处是后面的处理可以把整个Struct当做一个整体来传递而不是携带一堆散列的字段。5.3 explode对Struct不适用怎么办有一类问题经常把初学者难住Struct能不能用EXPLODE展开答案是EXPLODE的输入必须是数组或Map不能直接作用于Struct。但你可以把Struct包装成数组再展开SELECT uid, s FROM user_event_log LATERAL VIEW EXPLODE(ARRAY(base_info)) t AS s;这种包装看起来有点傻但在某些从上游拿到单条复合记录、又想按Struct字段标准化输出时它能帮你统一处理逻辑。另一种更通用的方式是直接用base_info.nickname、base_info.level分别取字段没必要真的把Struct炸开。在写业务代码前先想清楚你到底要行形态还是列形态能省不少功夫。5.4 Struct与JSON的互转思路很多日志本身是JSON格式其中某个字段解析后正好是Struct。Hive 3.x开始支持FROM_JSON和TO_JSON函数可以直接把JSON字符串转成Struct或者把Struct转回JSON字符串。SELECT FROM_JSON({nickname:alice,level:3}, nickname STRING, level INT) AS base_info;如果用的是TEXTFILE存储并且原始日志里就是JSON字符串你还是得靠get_json_object逐个字段解析。但如果表是ORC存储字段已经定义成Struct那就直接用点号访问不再需要和JSON字符串纠缠。我的经验是能规范化成Struct字段就不要一直留着JSON字符串。解析成本、可读性和性能差距都非常明显。6. 嵌套组合类型实战中大头在这里6.1 Array 埋点日志的标准玩法前面例子里的event_list就是最典型的Array 。处理它最常见需求是“筛选出满足条件的子集”比如只想保留用户的所有点击事件并把SKU聚合起来。Hive 3.0及以上支持高阶函数FILTER直接对数组中的每个元素做判断SELECT uid, FILTER(event_list, e - e.event_id click) AS click_events FROM user_event_log;这个写法的优势很明显不会产生中间行膨胀过滤发生在数组内部。如果你的Hive版本比较老没有高阶函数就只能用explode加聚合的笨办法SELECT uid, COLLECT_LIST(e) AS click_events FROM user_event_log LATERAL VIEW EXPLODE(event_list) t AS e WHERE e.event_id click GROUP BY uid;注意这里聚合回来的是StructCOLLECT_LIST完全可以存Struct没有限制。这种方案多了一步展开再聚合数据行数要先膨胀再收缩性能上不如高阶函数。6.2 Array多组标签怎么统计如果一张表里存的是每个用户的多个标签组每个标签组又是一个Map就会形成Array。展开顺序是从外到内先炸开数组拿到每个Map再炸开Map拿到键值对。SELECT uid, mp, k, v FROM user_event_log LATERAL VIEW EXPLODE(tag_groups) t1 AS mp LATERAL VIEW EXPLODE(mp) t2 AS k, v;这里有一个关键点后面的EXPLODE可以引用前面lateral view的输出列mp因为Hive解析表达式时后面的lateral view能看到前面已经产生的结果列。如果你遇到语法报错多半是别名层级写错了建议按t1、t2这种顺序命名一眼能看明白。6.3 Map / Map 的展开套路标签或属性如果非常复杂Map的value可能又是一个数组例如一个用户对每类商品的历史浏览SKU列表。SELECT uid, k, sku FROM user_event_log LATERAL VIEW EXPLODE(sku_map) t1 AS k, sku_arr LATERAL VIEW EXPLODE(sku_arr) t2 AS sku;Map的价值是数组时先炸Map拿到键和数组再炸数组拿到元素。顺序不能反否则你无法把内层元素和它的外层键对应起来。如果Map的value是Struct比如每个城市的用户画像访问时先炸Map拿到Struct再点字段SELECT uid, k, s.age, s.gender FROM user_event_log LATERAL VIEW EXPLODE(city_profile_map) t1 AS k, s;6.4 三层嵌套与高阶函数兜底真实生产数据经常出现三层甚至四层嵌套比如ArrayMapString, ArrayString。拆解的套路只有一个从外到内一层一个lateral view每一层都给清晰别名全程保留外层关键列。SELECT uid, m_key, s_item FROM user_log LATERAL VIEW EXPLODE(deep_arr) t1 AS inner_map LATERAL VIEW EXPLODE(inner_map) t2 AS m_key, m_value_arr LATERAL VIEW EXPLODE(m_value_arr) t3 AS s_item;如果嵌套太深且需要针对内层元素做复杂过滤我建议先用高阶函数在最内层数组上做处理再统一展开这样可以显著减少展开后的行数。实在不行再退回到逐层展开的老方案。嵌套深度一旦超过四层就可以考虑是不是建模有问题会在第8章再展开说。7. lateral view explode的进阶与坑7.1 多个explode之间是笛卡尔积很多人以为多个EXPLODE是按位置对齐的其实不是。多个lateral view之间遵循的是CROSS JOIN语义行数会相乘。SELECT uid, a_val, b_val FROM ( SELECT u01 AS uid, ARRAY(1,2) AS arr_a, ARRAY(x,y,z) AS arr_b ) t LATERAL VIEW EXPLODE(arr_a) a AS a_val LATERAL VIEW EXPLODE(arr_b) b AS b_val;这个结果会有6行也就是2乘以3。如果业务上需要按位置一一对应不能用多EXPLODE直接做必须用POSEXPLODE取位置索引再对齐。7.2 posexplode按位置对齐POSEXPLODE会额外输出一个位置索引列可以用来做按位对齐。SELECT uid, a_val, b_val FROM ( SELECT u01 AS uid, ARRAY(1,2) AS arr_a, ARRAY(x,y) AS arr_b ) t LATERAL VIEW POSEXPLODE(arr_a) ta AS a_idx, a_val LATERAL VIEW POSEXPLODE(arr_b) tb AS b_idx, b_val WHERE a_idx b_idx;这个写法只适合两个数组长度一致的情况。如果长度不一致多出来的部分会被过滤掉。是否需要保留取决于业务语义比如“如果数组B更长保留NULL对齐位”的场景还需要再补一层判断。7.3 OUTER关键字解决丢行问题EXPLODE一个空数组或空Map时这一行不会产生任何结果原始行也会被丢掉。这在很多统计场景里会造成“用户数变少”的假象。解决办法是加OUTERSELECT uid, e FROM user_event_log LATERAL VIEW OUTER EXPLODE(event_list) t AS e;加上OUTER之后即使事件列表为空原始行也会保留e为NULL。这在做“有多少用户没有任何事件”这类分析时特别有用。这个关键字非常容易被忽略建议凡是展开后要做全量用户统计的SQL都检查一下是否需要用OUTER。7.4 展开后别忘去重和过滤空值展开后的结果经常出现重复数据。比如多个事件都指向同一个SKU展开后SKU就会重复如果你只是想要“这个用户看过哪些SKU”直接用SELECT DISTINCT或者在外面套一层GROUP BY。另外展开出的元素可能出现NULL比如数组本身含有NULL元素。对NULL元素做字段访问时整个表达式都会返回NULL。我的习惯是在展开之后立刻加一个WHERE e IS NOT NULL避免下游统计出现莫名其妙的空行。8. 避坑手册与性能优化建议8.1 最容易犯的几个SQL写法错误这里把我在实际评审和排错中见过最多的问题集中列一下。第一个错误在SELECT里直接写EXPLODE(event_list)同时又查其他列。Hive不允许EXPLODE和其他表达式混在同一个SELECT列表中必须使用LATERAL VIEW。如果你只查EXPLODE这一列没有其他列Hive也能跑但实际业务几乎不可能只查一列所以请直接养成用LATERAL VIEW的习惯。第二个错误展开Struct数组后在WHERE里直接写event_id click而是应该写e.event_id click。Hive对这个错误基本都会报“Invalid table alias or column reference”定位到问题本身很快但新手往往不知道是e.丢掉了。第三个错误过滤Map展开后的键名时在子查询外的WHERE里使用k、v结果报“Column not found”。这不是Hive的锅是列的作用域问题展开产生的列只能在LATERAL VIEW所在层级的子查询里访问。第四个错误用ARRAY_CONTAINS判断Struct数组是否包含某个字段值忽略了类型匹配问题。数组里是Struct你拿字符串去比较编译就过不去。8.2 性能调优把过滤往前压EXPLODE是个行膨胀操作一放大行数下游所有计算成本都会成倍增加。所以性能优化的第一原则是把过滤尽量放在展开之前。如果你只想看SKU为sku_001的事件不要先炸开全部事件再WHERE过滤而是先用高阶函数过滤数组或者至少用LATERAL VIEW EXPLODE (FILTER(...))。SELECT uid, e.sku FROM user_event_log LATERAL VIEW EXPLODE(FILTER(event_list, e - e.sku sku_001)) t AS e;如果只需要数组里第一个元素用event_list[0]不要EXPLODE之后再LIMIT扫描和膨胀的成本完全不一样。同一份数据合理使用FILTER和下标访问任务时间可以差出好几倍。8.3 复杂类型拆还是留模型设计经验结合多年的实操体会我总结了几条复杂类型模型设计的经验。嵌套层数尽量控制在三层以内。超过三层不管是SQL可读性、维护成本还是执行性能都会快速恶化。遇到四层以上的数组套Map套数组考虑先落成中间表。复杂类型字段最好配合ORC或Parquet存储。TEXTFILE加LazySimpleSerDe对嵌套支持有限分隔符冲突和解析错误的概率高。同一张表里高频参与join的字段不要设计成Map或Structjoin条件在复杂类型上很难走优化通常需要先展开成普通列再join。给每个复杂类型字段写清楚注释包括结构和示例值。这在团队协作里非常重要比任何文档都直观。8.4 常用函数速查表最后把我的日常函数清单整理成一张表方便查阅。函数用途返回类型注意事项SIZE数组/Map长度IntNULL返回NULL空数组返回0ARRAY_CONTAINS判断数组是否包含元素Boolean类型必须完全匹配SORT_ARRAY数组排序ArrayStruct或Map不可排序EXPLODE数组/Map展开多行空输入丢行可用OUTER保留POSEXPLODE展开并带位置索引多行适合按位置对齐数组COLLECT_LIST聚合为数组Array不去重顺序不保证COLLECT_SET聚合为数组并去重Array顺序不保证MAP_KEYS取Map全部键Array键的顺序不保证MAP_VALUES取Map全部值Array值的顺序不保证STR_TO_MAP字符串转MapMap注意分隔符选择NAMED_STRUCT构造StructStruct字段名和值成对出现FILTER高阶函数过滤数组元素ArrayHive 3.0TRANSFORM高阶函数数组元素映射ArrayHive 3.0FROM_JSONJSON字符串转Struct/Array对应类型Hive 3.0需提供schema这套函数组合在一起基本可以覆盖复杂类型90%以上的操作需求。我个人在项目里最常用的是Array 和Map这两种形态Struct往往用于承载单条复合信息。建模阶段务必控制嵌套深度写查询时先做小样再上集群。最后分享一个调试技巧随便从一个表里SELECT一条带复杂类型的记录然后在旁边临时构造几行相同结构的STACK数据把你写的SQL先在小数据上跑一遍观察展开前后行数和列各是什么形态比对着文档反复猜快得多。
阅读完成 · 觉得有帮助?
咨询建站