数组展开与扁平化实战指南


快速选型

函数用途空数组行为适用场景
explode
explode
数组展开为多行丢弃该行标准展开,不需要保留空数组
explode_outer
explode_outer
数组展开为多行保留 NULL 行需要保留无数据的用户/记录
posexplode
posexplode
展开数组 + 获取索引丢弃该行需要知道元素在原数组中的位置
unnest
unnest
多列数组同步展开丢弃该行键值对数组、多维数组展开
flatten
flatten
嵌套数组扁平化二维数组转一维数组
explode_json_array_string
explode_json_array_string
JSON 字符串数组展开丢弃该行JSON 日志中的标签数组
explode_json_array_int
explode_json_array_int
JSON 整数数组展开丢弃该行JSON 中的 ID 列表
explode_json_array_json
explode_json_array_json
JSON 对象数组展开丢弃该行JSON 中的嵌套对象数组

前置准备

本文所有示例基于以下测试数据:

-- 用户标签表:每个用户有多个标签,存储为数组 CREATE TABLE user_tags ( user_id BIGINT, tags ARRAY<VARCHAR>, scores ARRAY<INT>, metadata VARCHAR -- JSON 字符串: {"tags": ["a", "b"], "ids": [1, 2]} ); INSERT INTO user_tags VALUES (1, ARRAY['vip', 'active', 'buyer'], ARRAY[100, 80, 90], '{"tags": ["a", "b"], "ids": [1, 2]}'), (2, ARRAY['new_user'], ARRAY[50], '{"tags": ["c"], "ids": [3]}'), (3, ARRAY[]::ARRAY<VARCHAR>, ARRAY[]::ARRAY<INT>, '{"tags": [], "ids": []}');


场景 1:基础展开(explode)

问题

将每个用户的标签数组展开为多行,便于统计每个标签的用户数。

SQL 实现

SELECT user_id, tag FROM user_tags LATERAL VIEW explode(tags) t AS tag;

输出:

user_idtag
1vip
1active
1buyer
2new_user

统计每个标签的用户数

SELECT tag, COUNT(DISTINCT user_id) AS user_count FROM user_tags LATERAL VIEW explode(tags) t AS tag GROUP BY tag ORDER BY user_count DESC;

输出:

taguser_count
vip1
active1
buyer1
new_user1

场景 2:保留空数组行(explode_outer)

问题

需要保留没有标签的用户,用于对比分析。

SQL 实现

SELECT user_id, tag FROM user_tags LATERAL VIEW explode_outer(tags) t AS tag;

输出:

user_idtag
1vip
1active
1buyer
2new_user
3NULL

场景 3:带索引展开(posexplode)

问题

需要知道标签在原数组中的位置(如优先级排序)。

SQL 实现

SELECT user_id, pos, tag FROM user_tags LATERAL VIEW posexplode(tags) t AS pos, tag;

输出:

user_idpostag
10vip
11active
12buyer
20new_user

关联多个数组

-- 标签和分数按位置对应 SELECT u.user_id, t.tag, s.score FROM user_tags u LATERAL VIEW posexplode(u.tags) t AS pos, tag LATERAL VIEW posexplode(u.scores) s AS spos, score WHERE t.pos = s.spos;

输出:

user_idtagscore
1vip100
1active80
1buyer90
2new_user50

场景 4:多列数组同步展开(unnest)

问题

将键值对数组同步展开,保持对应关系。

SQL 实现

SELECT user_id, k, v FROM user_tags, unnest(tags, scores) AS t(k, v);

输出:

user_idkv
1vip100
1active80
1buyer90
2new_user50

场景 5:嵌套数组扁平化(flatten)

问题

将二维数组(如多个用户的标签合并后)扁平化为一维数组。

SQL 实现

SELECT flatten(ARRAY[ARRAY['vip', 'active'], ARRAY['buyer'], ARRAY['new_user']]) AS all_tags;

输出:

all_tags
vip:active:buyer:new_user

结合 explode 使用

-- 先扁平化再展开 SELECT tag FROM ( SELECT flatten(ARRAY[ARRAY['vip', 'active'], ARRAY['buyer']]) AS all_tags ) LATERAL VIEW explode(all_tags) t AS tag;

输出:

tag
vip
active
buyer

场景 6:JSON 数组展开

问题

从 JSON 字符串中提取并展开数组字段。

SQL 实现

-- 需要先 PARSE_JSON 转为 JSON 类型 WITH parsed AS ( SELECT user_id, PARSE_JSON(metadata) AS meta FROM user_tags ) SELECT user_id, tag FROM parsed LATERAL VIEW explode_json_array_string(meta.tags) t AS tag;

输出:

user_idtag
1a
1b
2c

JSON 整数数组展开

WITH parsed AS ( SELECT user_id, PARSE_JSON(metadata) AS meta FROM user_tags ) SELECT user_id, id FROM parsed LATERAL VIEW explode_json_array_int(meta.ids) t AS id;

输出:

user_idid
11
12
23

常见问题

1.
explode
explode
vs
explode_outer
explode_outer
的选择

-- explode: 空数组行被丢弃 SELECT user_id, tag FROM user_tags LATERAL VIEW explode(tags) t AS tag; -- 用户 3 不在结果中 -- explode_outer: 空数组行保留为 NULL SELECT user_id, tag FROM user_tags LATERAL VIEW explode_outer(tags) t AS tag; -- 用户 3 在结果中,tag = NULL

2.
explode_json_array_*
explode_json_array_*
需要 JSON 类型

-- 错误: 直接传字符串 LATERAL VIEW explode_json_array_string('["a", "b"]') t AS tag -- 正确: 先 PARSE_JSON LATERAL VIEW explode_json_array_string(PARSE_JSON('["a", "b"]')) t AS tag

3.
unnest
unnest
要求数组长度一致

-- 如果数组长度不同,以最短的为准,多余元素被忽略 SELECT unnest(ARRAY['a', 'b', 'c'], ARRAY[1, 2]) AS t(k, v); -- 只展开 2 行: (a,1), (b,2)

4.
posexplode
posexplode
索引从 0 开始

-- 索引是 0-based,不是 1-based SELECT pos, tag FROM user_tags LATERAL VIEW posexplode(tags) t AS pos, tag; -- pos = 0, 1, 2 ...

5. 多次 LATERAL VIEW 的笛卡尔积

-- 错误: 两个 explode 会产生笛卡尔积 SELECT user_id, tag, score FROM user_tags LATERAL VIEW explode(tags) t AS tag LATERAL VIEW explode(scores) s AS score; -- 用户 1 会产生 3 x 3 = 9 行 -- 正确: 用 unnest 或 posexplode + WHERE SELECT user_id, k, v FROM user_tags, unnest(tags, scores) AS t(k, v);


性能优化建议

场景优化策略
大数组展开先用
filter
filter
缩小数组再展开
展开后聚合在 LATERAL VIEW 后立即
WHERE
WHERE
过滤
JSON 数组展开避免在展开前做复杂的 JSON 解析
多列展开优先用
unnest
unnest
而非多个
posexplode
posexplode
+
WHERE
WHERE

-- 推荐: 先过滤再展开 SELECT user_id, tag FROM user_tags LATERAL VIEW explode(filter(tags, t -> t != 'inactive')) t AS tag; -- 不推荐: 展开后再过滤(产生更多中间行) SELECT user_id, tag FROM user_tags LATERAL VIEW explode(tags) t AS tag WHERE tag != 'inactive';


相关文档

联系我们
预约咨询
微信咨询
电话咨询