Hive 数据仓库技术全解(二):HQL高级函数(窗口函数、UDF/UDAF/UDTF、炸裂函数与行列转换,分区/分桶)
Hive 数据仓库技术全解(二):HQL高级函数(窗口函数、UDF/UDAF/UDTF、炸裂函数与行列转换,分区/分桶)
作者:Lijingang
日期:2026-07-10
标签:HiveHadoop大数据SQLMapReduce窗口函数行列转换分区分桶UDF
本文为《Hive数据仓库技术全解》系列第二篇。
前篇已覆盖环境搭建、服务配置及 DDL/DML/DQL 基础。
本篇聚焦高阶 HQL 特性,包括窗口函数、UDF 自定义扩展、炸裂函数与行列转换、分区/分桶设计。
第三篇计划整理生产调优,涉及数据倾斜、小文件合并、Join 策略等
1. 内置函数
字符串函数
-- length():获取字符串长度
SELECT length('hello world'); -- 11
-- trim():去掉首尾空格
SELECT trim(' lijingang '); -- 'lijingang'
-- substring(str,start,stop):从第二个元素开始截取,包含第二个
SELECT substring('lijingang', 2); -- 'ijingang'
-- replace():替换函数
SELECT replace('lijingang', 'jin', 'J'); -- 'liJgang'
-- regexp_replace():正则替换
SELECT regexp_replace('tel:1392902397', '\\d+', '****'); -- 'tel:****'
-- concat():拼接函数
SELECT concat('a', '-', 'b', '-', 'c'); -- 'a-b-c'
-- concat_ws():第一个参数为拼接符,其余参数拼接
SELECT concat_ws('-', 'a', 'b', 'c'); -- 'a-b-c'
-- split():分割函数,按照指定分隔符分割
SELECT split('a-b-c-d', '-'); -- ["a","b","c","d"]
-- nvl():如果为null则填充第二个参数
SELECT nvl(NULL, 0); -- 0
-- get_json_object(): 解析json对象
SELECT get_json_object(json_str, '$.[0].name'); -- 解析 JSON
日期函数
SELECT unix_timestamp(); -- 当前时间戳
SELECT from_unixtime(1755734400,'yyyy-MM-dd'); -- 时间戳转日期
-- datediff():两个日期相差天数,第一个参数减去第二个参数
SELECT datediff('2025-08-12', '2025-08-10'); -- 2
-- date_add(date,n):date加上n天的日期
SELECT date_add('2025-08-10', 3); -- '2025-08-13'
SELECT current_date(); -- 当前日期
SELECT year('2025-08-10 11:12:13'); -- 2025
流程控制函数
-- 统计不同部门男女个数
/*
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
[ELSE resultN]
END
*/
SELECT deptId,
SUM(CASE sex WHEN '男' THEN 1 ELSE 0 END) AS `男`,
SUM(CASE sex WHEN '女' THEN 1 ELSE 0 END) AS `女`
FROM emp_sex
GROUP BY deptId;
-- IF 函数
/*
if condition ,result1 , result2
condition成立 -> result1, 不成立 -> result2
*/
SELECT deptId,
SUM(IF(sex='男', 1, 0)) AS `男`,
SUM(IF(sex='女', 1, 0)) AS `女`
FROM emp_sex
GROUP BY deptId;
2. 高级聚合与炸裂函数
高级聚合函数(行转列,UDAF)
-- collect_list:聚合为不去重列表
SELECT sex, collect_list(job) FROM employee GROUP BY sex;
-- collect_set:聚合为去重集合
SELECT sex, collect_set(job) FROM employee GROUP BY sex;
炸裂函数(列转行,UDTF)
-- explode(array):将数组每个元素展开为一行
SELECT explode(array(1, 3, 4, 6)) AS item;
-- explode(map):将 map 每个键值对展开为一行
SELECT explode(map('name', 'zhangsan', 'age', '18'));
-- posexplode:展开的同时记录下标
SELECT posexplode(array('zhangsan', 'lisi', 'wangwu')) AS (index, value);
-- inline:展开 struct 数组
SELECT inline(array(named_struct('name','zhangsan','age',18)));
-- LATERAL VIEW:炸裂的同时保留原表字段的对应关系
SELECT name, fri
FROM employee
LATERAL VIEW explode(friends) tmp AS fri;
实战案例 —— 统计电影分类 Top3:
-- 表结构:name(电影名)、category(分类,逗号分隔)
/*
《疑犯追踪》,"悬疑,动作,科幻,剧情"
《Lie to me》,"悬疑,警匪,动作,心理,剧情"
《战狼2》,"战争,动作,灾难"
*/
-- 需求:统计不同分类的电影数量,取 Top3
SELECT type, cnt
FROM (
SELECT type, COUNT(*) cnt
FROM (
SELECT name, type
FROM movie_info
LATERAL VIEW explode(split(category, ',')) tmp AS type
) t1
GROUP BY type
) t2
ORDER BY cnt DESC
LIMIT 3;
3. 窗口函数
窗口函数是 HQL 中最强大的特性之一,可以在不改变行数的前提下进行分组计算。
-- 语法骨架
SELECT 字段, 窗口函数 OVER (
PARTITION BY 分区字段
ORDER BY 排序字段
[ROWS|RANGE BETWEEN start AND end]
)
FROM table_name;
窗口缺省规则:
- 无 ORDER BY →
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - 有 ORDER BY →
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
常用窗口函数:
| 函数 | 作用 |
|---|---|
SUM / COUNT / AVG / MAX / MIN | 聚合计算 |
LAG(col, n, default) | 取前第 n 行 |
LEAD(col, n, default) | 取后第 n 行 |
FIRST_VALUE / LAST_VALUE | 窗口内的首个/末个值 |
RANK / DENSE_RANK / ROW_NUMBER | 排名 |
实战案例
表结构:
| order_id | user_id | user_name | order_date | order_amount |
|---|---|---|---|---|
| 1 | 1001 | 小元 | 2022-01-01 | 10 |
| 2 | 1002 | 小海 | 2022-01-02 | 15 |
| 3 | 1001 | 小元 | 2022-02-03 | 23 |
| 4 | 1002 | 小海 | 2022-01-04 | 29 |
| 5 | 1001 | 小元 | 2022-01-05 | 46 |
| 6 | 1001 | 小元 | 2022-04-06 | 42 |
| 7 | 1002 | 小海 | 2022-01-07 | 50 |
| 8 | 1001 | 小元 | 2022-01-08 | 50 |
| 9 | 1003 | 小辉 | 2022-04-08 | 62 |
| 10 | 1003 | 小辉 | 2022-04-09 | 62 |
| 11 | 1004 | 小猛 | 2022-05-10 | 12 |
| 12 | 1003 | 小辉 | 2022-04-11 | 75 |
| 13 | 1004 | 小猛 | 2022-06-12 | 80 |
| 14 | 1003 | 小辉 | 2022-04-13 | 94 |
-- 1. 累计消费金额
SELECT order_id, user_id, order_amount,
SUM(order_amount) OVER (
PARTITION BY user_id
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS total_amount
FROM order_info;
-- 2. 距离上次下单的天数
SELECT user_id, order_date,
DATEDIFF(order_date,
LAG(order_date, 1, '首次下单') OVER (
PARTITION BY user_id, SUBSTR(order_date, 1, 7)
ORDER BY order_date
)
) AS day_num
FROM order_info;
-- 3. RANK / DENSE_RANK / ROW_NUMBER 区别
-- 成绩:100, 100, 99
-- RANK() → 1, 1, 3 (同分同名次,后续跳过)
-- DENSE_RANK() → 1, 1, 2 (同分同名次,后续不跳过)
-- ROW_NUMBER() → 1, 2, 3 (纯行号,不关心值)
4. 自定义函数(UDF)
当内置函数无法满足需求时,可以编写自定义函数:
自己计算字符串的长度
// 自定义类继承 Hive 的 UDF 类,重写 evaluate 方法
package com.lijingang.HiveFun;
import org.apache.hadoop.hive.ql.exec.UDF;
public class MyHiveFun extends UDF {
public int evaluate(String str) {
return str == null ? 0 : str.length();
}
}
避坑提示:自定义 UDF 时,evaluate 方法必须为 public,且返回值类型要和 Hive 数据类型映射一致(如 String 映射为 VARCHAR)。如果传入参数为 null,记得做空指针防御,否则上线后容易报 NullPointerException。
-- 加载 jar 包
ADD JAR /opt/module/hive-3.1.3/my_funs/HiveFun-1.0-SNAPSHOT.jar;
-- 创建临时函数(重连后失效)
CREATE TEMPORARY FUNCTION my_len AS 'com.lijingang.HiveFun.MyHiveFun';
-- 创建永久函数(需将 jar 包上传到 HDFS)
CREATE FUNCTION my_len AS 'com.lijingang.HiveFun.MyHiveFun'
USING JAR 'hdfs://hadoop102:8020/udf/HiveFun-1.0-SNAPSHOT.jar';
-- 使用
SELECT my_len('hello'); -- 5
补充
除了上述自定义 UDF(一对一),Hive 还支持自定义 UDAF(多对一聚合) 和 UDTF(一对多生成)。它们的实现需分别继承 AbstractGenericUDAFResolver 和 GenericUDTF,并实现更复杂的逻辑。生产环境中,复杂的多行处理(如自定义分组排序聚合)建议使用 UDAF,但大多数场景下,Hive 内置的 collect_set/explode 已能满足 90% 的需求,故本文不再展开源码,后续调优篇如有需要再做补充。
5. 分区表与分桶表
分区表
将海量数据按照某一字段分目录存储,查询时结合分区字段可直接锁定目录,避免全表扫描。
-- 创建静态分区表
CREATE TABLE dept_partition (
deptno INT,
dname STRING,
location STRING
)
PARTITIONED BY (day STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
-- 按分区导入数据
LOAD DATA LOCAL INPATH '/path/to/data.log'
INTO TABLE dept_partition
PARTITION (day='20260403');
-- 查询时利用分区过滤(不触发全表扫描)
SELECT * FROM dept_partition WHERE day = '20260403';
-- 分区操作
SHOW PARTITIONS dept_partition; -- 查看分区
ALTER TABLE dept_partition ADD PARTITION (day='20260405'); -- 添加分区
ALTER TABLE dept_partition DROP PARTITION (day='20260405'); -- 删除分区
MSCK REPAIR TABLE dept_partition SYNC PARTITIONS; -- 修复分区
动态分区
-- 开启非严格模式(动态分区必须)
SET hive.exec.dynamic.partition.mode = nonstrict;
-- 将普通表改造为动态分区表
INSERT INTO TABLE order_info_partition
PARTITION (user_id)
SELECT order_id, user_name, order_date, order_amount, user_id
FROM order_info;
分桶表
分桶是将数据按字段哈希分文件存储,适合两张大表 Join 时优化。
-- 分桶表
CREATE TABLE stu_bucket (
id INT,
name STRING
)
CLUSTERED BY (id) INTO 4 BUCKETS
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
-- 分桶有序表(桶内排序,进一步优化 Join)
CREATE TABLE stu_bucket_sort (
id INT,
name STRING
)
CLUSTERED BY (id) SORTED BY (id DESC) INTO 4 BUCKETS
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
分区表适用于按天/按业务维度筛选的场景(缩小扫描范围)。
分桶表适用于大表与大表 Join(避免数据倾斜,桶内数据均匀分布),以及抽样查询(TABLESAMPLE)。
如果分区后单个分区文件依然很大(>1GB),建议再建分桶。
5. 文件存储格式对比
| 格式 | 类型 | 特点 | 使用场景 |
|---|---|---|---|
| TEXTFILE | 行存 | 默认格式,可读性好 | 数据展示 |
| ORC | 列存 | 压缩比高,查询快 | 计算密集型(推荐) |
| Parquet | 列存 | 兼容性好,生态广泛 | 跨平台交互 |
| SequenceFile | 行存 | 二进制格式 | 已基本淘汰 |
📌 本文定位:个人学习笔记,聚焦 Hive 高级特性在面试及生产环境中的高频考点。内含大量实战 SQL 案例及踩坑总结,仅供参考。
更多推荐
所有评论(0)