数组与 Map 处理实战指南


快速选型

函数用途输入输出
transform
transform
数组元素逐一转换数组 + lambda新数组
filter
filter
按条件过滤数组元素数组 + lambda子数组
exists
exists
判断是否有元素满足条件数组 + lambdaBOOLEAN
forall
forall
判断是否所有元素满足条件数组 + lambdaBOOLEAN
array_aggregate
array_aggregate
数组内聚合计算数组 + 初始值 + lambda任意类型
zip_with
zip_with
两个数组按位置配对处理数组1 + 数组2 + lambda新数组
map_filter
map_filter
按条件过滤 Map 条目Map + lambda新 Map
transform_values
transform_values
转换 Map 的值Map + lambda新 Map
transform_keys
transform_keys
转换 Map 的键Map + lambda新 Map
map_zip_with
map_zip_with
合并两个 MapMap1 + Map2 + lambda新 Map

前置准备

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

-- 用户事件日志表:每个用户有多条事件,存储为数组 CREATE TABLE user_events ( user_id BIGINT, event_types ARRAY<VARCHAR>, -- 事件类型数组: ['view', 'click', 'purchase'] durations ARRAY<INT>, -- 对应事件时长数组: [10, 25, 100] tags ARRAY<VARCHAR> -- 用户标签数组: ['vip', 'new_user'] ); INSERT INTO user_events VALUES (1, ARRAY['view', 'click', 'view', 'purchase', 'view'], ARRAY[10, 25, 15, 100, 8], ARRAY['vip', 'active']), (2, ARRAY['click', 'view', 'click'], ARRAY[5, 30, 12], ARRAY['new_user']), (3, ARRAY['view', 'view', 'view'], ARRAY[20, 25, 15], ARRAY['vip', 'inactive']);


场景 1:日志事件数组过滤

问题

从事件类型数组中筛选出特定类型的事件,并获取对应的时长。

SQL 实现

SELECT user_id, event_types, durations, -- 过滤出 view 事件的时长(lambda 支持索引参数,1-based) filter(durations, (d, i) -> event_types[i] = 'view') AS view_durations, -- 过滤出 purchase 事件的时长 filter(durations, (d, i) -> event_types[i] = 'purchase') AS purchase_durations FROM user_events;

输出:

user_idevent_typesdurationsview_durationspurchase_durations
1view:click:view:purchase:view10:25:15:100:810:15:8100
2click:view:click5:30:1230
3view:view:view20:25:1520:25:15

关键说明

  • filter
    filter
    的 lambda 支持两个参数:
    (element, index)
    (element, index)
    ,索引从 1 开始
  • 可以通过索引关联多个数组(如
    event_types[i]
    event_types[i]
    关联
    durations[i]
    durations[i]

场景 2:条件判断(exists / forall)

问题

判断用户是否发生过特定行为,或是否所有行为都符合某种模式。

SQL 实现

SELECT user_id, event_types, -- 是否有 purchase 事件 exists(event_types, t -> t = 'purchase') AS has_purchase, -- 是否所有事件都是 view forall(event_types, t -> t = 'view') AS all_views, -- 是否有时长超过 20 的事件 exists(durations, d -> d > 20) AS has_long_session FROM user_events;

输出:

user_idevent_typeshas_purchaseall_viewshas_long_session
1view:click:view:purchase:viewtruefalsetrue
2click:view:clickfalsefalsetrue
3view:view:viewfalsetruetrue

场景 3:数组元素转换(transform)

问题

对数组中每个元素进行统一转换,如单位换算、格式化等。

SQL 实现

SELECT user_id, durations, -- 毫秒转秒(假设原始数据是毫秒) transform(durations, d -> d * 1000) AS durations_ms, -- 时长分级 transform(durations, d -> CASE WHEN d >= 30 THEN 'long' WHEN d >= 10 THEN 'medium' ELSE 'short' END ) AS duration_levels FROM user_events;

输出:

user_iddurationsdurations_msduration_levels
110:25:15:100:810000:25000:15000:100000:8000medium:long:medium:long:short
25:30:125000:30000:12000short:long:medium
320:25:1520000:25000:15000medium:long:medium

场景 4:数组内聚合(array_aggregate)

问题

在数组内部进行聚合计算,如求和、最大值、计数等,无需展开为多行。

SQL 实现

SELECT user_id, durations, -- 总时长 array_aggregate(durations, 0, (acc, x) -> acc + x) AS total_duration, -- 最大时长 array_aggregate(durations, 0, (acc, x) -> CASE WHEN x > acc THEN x ELSE acc END) AS max_duration, -- 事件数量(数组长度) array_aggregate(durations, 0, (acc, x) -> acc + 1) AS event_count FROM user_events;

输出:

user_iddurationstotal_durationmax_durationevent_count
110:25:15:100:81581005
25:30:1247303
320:25:1560253

关键说明

  • array_aggregate(array, initial, (acc, x) -> expr)
    array_aggregate(array, initial, (acc, x) -> expr)
    三参数形式最稳定
  • 四参数形式(带 finish lambda)在字符串操作时可能有 codegen 限制,建议用数值聚合

场景 5:双数组配对处理(zip_with)

问题

将两个等长数组按位置配对,进行逐元素计算。

SQL 实现

SELECT user_id, event_types, durations, -- 计算每个事件的"单位时长价值"(假设 purchase 价值 100,click 价值 10,view 价值 1) zip_with( event_types, durations, (t, d) -> d * CASE t WHEN 'purchase' THEN 100 WHEN 'click' THEN 10 ELSE 1 END ) AS event_values FROM user_events;

输出:

user_idevent_values
110:250:15:10000:8
250:30:120
320:25:15

场景 6:Map 数据过滤与转换

问题

处理键值对数据(如用户画像标签、配置参数),按条件过滤或转换。

SQL 实现

-- 使用 MAP_FROM_ARRAYS 创建 Map(推荐方式) WITH user_profiles AS ( SELECT 1 AS user_id, MAP_FROM_ARRAYS(ARRAY['age', 'city', 'tier'], ARRAY[25, 1, 3]) AS profile UNION ALL SELECT 2, MAP_FROM_ARRAYS(ARRAY['age', 'city', 'tier'], ARRAY[30, 2, 1]) UNION ALL SELECT 3, MAP_FROM_ARRAYS(ARRAY['age', 'city', 'tier'], ARRAY[22, 1, 2]) ) SELECT user_id, profile, -- 过滤出 tier > 1 的用户画像条目 map_filter(profile, (k, v) -> k = 'tier' AND v > 1) AS high_tier_filter, -- 将 age 值翻倍 transform_values(profile, (k, v) -> CASE WHEN k = 'age' THEN v * 2 ELSE v END ) AS doubled_age, -- 将键名转为大写 transform_keys(profile, (k, v) -> UPPER(k)) AS upper_keys FROM user_profiles;

输出:

user_idprofilehigh_tier_filterdoubled_ageupper_keys
1age=25:city=1:tier=3tier=3age=50:city=1:tier=3AGE=25:CITY=1:TIER=3
2age=30:city=2:tier=1age=60:city=2:tier=1AGE=30:CITY=2:TIER=1
3age=22:city=1:tier=2tier=2age=44:city=1:tier=2AGE=22:CITY=1:TIER=2

场景 7:合并两个 Map(map_zip_with)

问题

合并两个 Map,对相同键的值进行聚合(如累加、取最大)。

SQL 实现

SELECT map_zip_with( MAP_FROM_ARRAYS(ARRAY['a', 'b'], ARRAY[1, 2]), MAP_FROM_ARRAYS(ARRAY['a', 'c'], ARRAY[10, 30]), (k, v1, v2) -> COALESCE(v1, 0) + COALESCE(v2, 0) ) AS merged;

输出:

merged
a=11:b=2:c=30

场景 8:综合实战 — 用户行为标签计算

问题

基于用户事件数组,计算用户行为标签:

  • 是否有购买行为
  • 平均会话时长
  • 是否高频用户(事件数 ≥ 4)
  • 主要行为类型(出现最多的事件)

SQL 实现

SELECT user_id, event_types, durations, -- 购买标记 CASE WHEN exists(event_types, t -> t = 'purchase') THEN 'buyer' ELSE 'non_buyer' END AS buyer_flag, -- 平均时长(总时长 / 事件数) array_aggregate(durations, 0, (acc, x) -> acc + x) / array_aggregate(durations, 0, (acc, x) -> acc + 1) AS avg_duration, -- 高频标记 CASE WHEN array_aggregate(durations, 0, (acc, x) -> acc + 1) >= 4 THEN 'high_freq' ELSE 'low_freq' END AS freq_flag, -- 标签数组转换 transform(tags, t -> CONCAT('tag_', t)) AS prefixed_tags FROM user_events;

输出:

user_idbuyer_flagavg_durationfreq_flagprefixed_tags
1buyer31high_freqtag_vip:tag_active
2non_buyer15low_freqtag_new_user
3non_buyer20low_freqtag_vip:tag_inactive

常见问题

1.
MAP()
MAP()
函数创建类型错误

-- 错误: MAP(ARRAY, ARRAY) 创建的是 map<array<string>, array<int>> MAP(ARRAY['a', 'b'], ARRAY[1, 2]) -- 类型不对 -- 正确: 使用 MAP_FROM_ARRAYS MAP_FROM_ARRAYS(ARRAY['a', 'b'], ARRAY[1, 2]) -- 正确类型 map<string, int>

2. Lambda 索引从 1 开始

-- filter 的索引参数从 1 开始,不是 0 filter(durations, (d, i) -> event_types[i] = 'view') -- i=1 对应第一个元素

3.
array_aggregate
array_aggregate
finish 函数的 codegen 限制

-- 可能报错: finish 函数中使用字符串操作(内部 CONCAT 不支持 codegen) array_aggregate(ARRAY['a', 'b'], '', (acc, x) -> acc || x, s -> CONCAT('[', s, ']')) -- 推荐: 数值聚合稳定,字符串操作建议在聚合后用 transform 处理 array_aggregate(ARRAY[1, 2, 3], 0, (acc, x) -> acc + x) -- 稳定

4.
exists
exists
vs
forall
forall
的短路逻辑

  • exists
    exists
    : 找到第一个满足条件的元素即返回 true
  • forall
    forall
    : 找到第一个不满足条件的元素即返回 false
  • 空数组:
    exists
    exists
    返回 false,
    forall
    forall
    返回 true

5.
zip_with
zip_with
要求数组长度一致

-- 如果数组长度不同,多余元素会被忽略或填充 NULL(取决于实现) zip_with(ARRAY[1, 2], ARRAY[10, 20, 30], (x, y) -> x + y) -- 第三个元素可能丢失


性能优化建议

场景优化策略
大数组过滤先用
filter
filter
缩小数组,再进行
transform
transform
或聚合
频繁 exists 检查考虑将高频检查字段提取为独立列,避免每次扫描数组
数组聚合
array_aggregate
array_aggregate
explode
explode
+
GROUP BY
GROUP BY
更高效(无需 shuffle)
Map 操作使用
map_filter
map_filter
先过滤再处理,减少不必要的键值对计算

-- 推荐: 先过滤再转换(减少处理元素数) transform(filter(durations, d -> d > 10), d -> d * 2) -- 不推荐: 先转换再过滤(所有元素都经过 transform) filter(transform(durations, d -> d * 2), d -> d > 20)


相关文档

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