📑 目录
- [1. 概述](#1. 概述)
- [2. 语法与功能](#2. 语法与功能)
- [3. COLLECT_LIST vs COLLECT_SET:核心区别](#3. COLLECT_LIST vs COLLECT_SET:核心区别)
- [4. 实战示例](#4. 实战示例)
- [5. 如何控制 COLLECT_LIST 的顺序](#5. 如何控制 COLLECT_LIST 的顺序)
- [6. 注意事项与性能优化](#6. 注意事项与性能优化)
- [7. 扩展用法](#7. 扩展用法)
- [8. 总结](#8. 总结)
1. 概述
COLLECT_LIST 是 Hive 中一个非常实用的聚合函数,它能够将分组内某列的所有值"收集"起来,打包成一个数组(ARRAY 类型)返回。它就像是给数据拍了一张"集体照",把同一组里的所有成员都放进了同一张图片里。
它是 Hive 实现"行转列"功能的核心函数,常与 GROUP BY 语句配合使用,将原本多行的数据压缩到一行的一个字段中。
2. 语法与功能
COLLECT_LIST 的语法非常简洁:
sql
COLLECT_LIST( [DISTINCT] column_name ) -- Hive 3.0 之后支持 DISTINCT 关键字,但更推荐使用 COLLECT_SET
该函数接受一个列名作为参数,将其所有值(包括重复值)放入一个列表(数组)中。
功能特点:
- 保留重复值 :与它的"兄弟"函数
COLLECT_SET不同,COLLECT_LIST会忠实地保留分组内所有的数据,即使它们是完全重复的。 - 保留输入顺序 :通常情况下,结果数组中的元素顺序与原始数据输入的顺序一致。但此顺序在执行大规模分布式任务时可能不稳定,因此在实际应用中,如果需要保证顺序,建议结合
ORDER BY或SORT BY使用。 - 忽略 NULL 值 :当
column_name的值为NULL时,该行不会被计入数组中。
3. COLLECT_LIST vs COLLECT_SET:核心区别
Hive 中有两个"收集"函数:COLLECT_LIST 和 COLLECT_SET。它们的主要区别在于是否对结果去重。
| 特性 | COLLECT_LIST |
COLLECT_SET |
|---|---|---|
| 处理重复值 | 保留所有值(包括重复) | 自动去重,仅保留唯一值 |
| 结果顺序 | 相对有序,基本按输入顺序排列 | 无序,结果的顺序不确定 |
| 返回类型 | ARRAY |
ARRAY |
| 使用场景 | 需要保留所有明细记录的场景,如用户的所有订单历史 | 需要统计不重复项的场景,如用户购买过的商品品类 |
一句话总结 :当需要保留所有值,包括重复值时,用
COLLECT_LIST;当需要去除重复值,获得唯一列表时,用COLLECT_SET。
4. 实战示例
假设我们有一张记录了用户观看视频历史的表 user_video_log:
| user_id | video_name | watch_date |
|---|---|---|
| 101 | "Hive Tutorial" |
2024-01-01 |
| 101 | "Spark Guide" |
2024-01-02 |
| 101 | "Hive Tutorial" |
2024-01-03 |
| 102 | "Kafka 101" |
2024-01-01 |
| 102 | "Kafka 101" |
2024-01-05 |
场景一:统计每个用户看过的所有视频(保留重复)
sql
-- 使用 COLLECT_LIST,将保留用户101观看的两次 'Hive Tutorial'
SELECT
user_id,
COLLECT_LIST(video_name) AS all_videos
FROM user_video_log
GROUP BY user_id;
查询结果:
| user_id | all_videos |
|---|---|
| 101 | ["Hive Tutorial", "Spark Guide", "Hive Tutorial"] |
| 102 | ["Kafka 101", "Kafka 101"] |
场景二:统计每个用户看过哪些不同的视频(去重)
sql
-- 使用 COLLECT_SET,将自动去除 'Hive Tutorial' 的重复
SELECT
user_id,
COLLECT_SET(video_name) AS unique_videos
FROM user_video_log
GROUP BY user_id;
查询结果:
| user_id | unique_videos |
|---|---|
| 101 | ["Hive Tutorial", "Spark Guide"] |
| 102 | ["Kafka 101"] |
5. 如何控制 COLLECT_LIST 的顺序
在实际业务中,我们常常需要按某个特定顺序(如时间顺序)来排列聚合后的数组。由于 COLLECT_LIST 的顺序在分布式环境中可能不稳定,因此需要主动控制。
方法一:子查询中使用 SORT BY 或 ORDER BY(推荐)
sql
SELECT
user_id,
COLLECT_LIST(video_name) AS ordered_videos,
COLLECT_LIST(watch_date) AS ordered_dates
FROM (
SELECT user_id, video_name, watch_date
FROM user_video_log
DISTRIBUTE BY user_id -- 确保同一 user_id 的数据被分到同一个 reducer 进行处理
SORT BY user_id, watch_date ASC -- 在该 reducer 内按 watch_date 升序排序
) t
GROUP BY user_id;
核心技巧 :在内层子查询中,使用 DISTRIBUTE BY 和 SORT BY 来控制数据进入 COLLECT_LIST 前的顺序。DISTRIBUTE BY 确保了相同 user_id 的数据被发送到同一个Reducer,SORT BY 则在该Reducer内部对数据进行排序,从而保证了聚合后的数组顺序正确。
方法二:使用 SORT_ARRAY 函数
sql
SELECT
user_id,
SORT_ARRAY(COLLECT_LIST(video_name)) AS sorted_videos
FROM user_video_log
GROUP BY user_id;
SORT_ARRAY 可以对数组进行全局的升序排序。这是一种更简洁的方法,但它的灵活性有限,只能对整个数组进行升序排序,无法实现复杂或自定义的排序逻辑。
6. 注意事项与性能优化
-
内存与性能风险 :
COLLECT_LIST会将分组内所有数据加载到内存中。如果某个分组的数据量巨大(例如为"用户ID"为空的数百万条记录),极易导致 OOM(内存溢出) 错误。建议在处理前先过滤掉这些异常数据或通过子查询限制数据量。 -
数据倾斜风险:当某些分组(Key)的数据量远超其他分组时,会导致负责处理该分组的 Reducer 任务成为整个作业的瓶颈,即数据倾斜。
-
使用
LIMIT控制大小 :如果业务允许,可以使用LIMIT子句来限制COLLECT_LIST收集的数据量,防止内存溢出。
7. 扩展用法
1. 行转列并拼接为字符串
这是 COLLECT_LIST 最经典的用法。将收集到的数组通过 CONCAT_WS 函数,用指定的分隔符拼接成一个字符串。
sql
SELECT
user_id,
CONCAT_WS(',', COLLECT_LIST(video_name)) AS video_list_str
FROM user_video_log
GROUP BY user_id;
2. 用 SIZE 函数统计数量
SIZE 函数可以返回数组的长度,即分组内元素的数量。
sql
SELECT
user_id,
SIZE(COLLECT_LIST(video_name)) AS watch_count
FROM user_video_log
GROUP BY user_id;
8. 总结
| 维度 | 核心内容 |
|---|---|
| 核心功能 | 将分组内一列的多行值,聚合为一个数组,实现"行转列"。 |
与 COLLECT_SET 的区别 |
COLLECT_LIST 保留重复值 ;COLLECT_SET 自动去重。 |
| 顺序控制 | 使用 DISTRIBUTE BY + SORT BY 精确控制;或使用 SORT_ARRAY 进行全局升序排序。 |
| 性能与风险 | 需警惕内存溢出 和数据倾斜 风险;建议过滤数据 、控制数据量 或增加内存。 |
| 扩展用法 | 常与 CONCAT_WS 拼接为字符串,或与 SIZE 函数统计数量。 |
COLLECT_LIST 是一个功能强大但需要谨慎使用的函数。理解其特性并掌握正确的使用技巧,能帮助你更高效地完成复杂的数据聚合任务。