Hive自定义函数(UDF)开发和应用流程.18981521
引言
Hive作为大数据领域的核心计算引擎,凭借其强大的SQL支持和丰富的内置函数,早已成为数据开发者的效率利器。然而在实际业务场景中,面对复杂的数据处理需求时,仅仅依赖内置函数往往力不从心,当需要实现多步骤逻辑组合(如日期换算+字符串清洗+条件判断)时,开发者常需反复调用add_months、replace、substr等多个函数,甚至嵌套多层case when。偶尔使用一两次还可接受,但在同一段HQL脚本中需要多次重复组合使用时,不仅容易因疏忽导致参数顺序错误或函数遗漏,还会让代码变得冗余繁杂,可读性与维护性大幅下降。
笔者近期在参与ODS→DW→DM数据链路开发时便深有体会,表数据按日期分区存储(分区格式为yyyyMM,如202507),数据随时间滚动更新,在汇总计算近3个月、6个月、12个月等周期指标时,需频繁对分区字段进行“日期换算+格式清洗”操作。这时候代码中充斥着 add_months(concat(substr(dt,1,4),'-',substr(dt,5,2)), -3) 这样的复杂表达式,不仅容易出错,更让SQL脚本变得惨不忍睹。
Hive自定义函数(UDF)的出现,正好解决这一痛点。通过将高频复用的业务逻辑封装为UDF,开发者不仅能扩展Hive 的计算边界,更能将原本需要多行代码实现的功能,浓缩为一行简洁的 SQL 调用。这不仅大幅减少了重复代码,更让业务逻辑在SQL中清晰可读,显著提升了开发效率与代码可维护性。下面是笔者针对日期换算需求实现UDF的过程。
一、Hive自定义函数的类型
Hive自定义函数可以通过Java/Scala语言实现,主要有下面几种自定义函数类型:
| 类型 | 特点 | 使用场景 |
|---|---|---|
| UDF | 单行输入 -> 单行输出(跟普通内置函数相似) | 简单的计算,例如字符串截取、字符替换等 |
| UDAF | 多行输入 -> 单行输出(类似聚合函数) | 自定义聚合功能数据逻辑,例如按条件统计个数或者做加权取平均值 |
| UDTF | 单行输入 -> 多行输出(跟lateral view explode功能相似) | 进行行列转换、数据拆分或者JSON之类的文本解析 |
在日常开发中大多数场景使用的都是UDF,这是实现复杂业务场景的首选,开发过程也简单。
二、准备环境和工具
1.准备开发环境和工具
OS: Windows 10
Java: 8
Hive: 2.7.4
IDEA:社区版
maven 3.9.11
软件安装步骤这里就省略了,网上基本都能搜索到相关安装教程。
2.MAVEN依赖配置POM.xml,添加Hive核心以来,确保与集群版本一致
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mycode</groupId>
<artifactId>SuperAddMonths</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hive.version>2.3.10</hive.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>${hive.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
三、实际案例开发编译
UDF示例代码SuperAddMonths.java
package org.mycode;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.IntObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.LongObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@Description(name = "super_add_months",
value = "计算日期调整月份数结果,输入yyyyMM格式日期及调整月份数,返回相同格式yyyyMM日期",
extended = "功能:将输入的 yyyyMM 格式日期(如 '202507')按指定月份数(如 1)调整,返回调整后的 yyyyMM 格式日期。\n" +
"参数说明:\n" +
" - 第1个参数:输入日期字符串(格式 yyyyMM,非空)\n" +
" - 第2个参数:调整月份数(整数,正数表示向后调整,负数表示向前调整,允许为 NULL)\n" +
"返回值:调整后的日期字符串(格式 yyyyMM)\n" +
"示例:\n" +
" SELECT super_add_months('202507', 1); → '202508'(2025年7月 +1个月 → 2025年8月)\n" +
" SELECT super_add_months('202507', -11); → '202408'(2025年7月 -11个月 → 2024年8月)\n" +
" SELECT super_add_months('202507', NULL); → '202507'(偏移量为 NULL 时返回原日期)")
public class SuperAddMonths extends GenericUDF {
private static final DateTimeFormatter INIT_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter TARGET_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMM");
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
if (arguments.length != 2) {
throw new UDFArgumentException("调用函数需要传入2个参数,实际传入"+arguments.length+"个参数");
}
ObjectInspector firstArg = arguments[0];
ObjectInspector secondArg = arguments[1];
if (!(firstArg instanceof StringObjectInspector)) {
throw new UDFArgumentException("第一个传入参数必须是字符串(日期格式为yyyyMM)");
}
if (!(secondArg instanceof LongObjectInspector || secondArg instanceof IntObjectInspector || secondArg == null)) {
throw new UDFArgumentException("第二个传入参数必须是整型数字");
}
return PrimitiveObjectInspectorFactory.writableStringObjectInspector;
}
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
// 获取第一个参数(字符串类型,Hive存储为Text)
Text monthText = (Text) arguments[0].get();
if (monthText == null) {
return null; // 输入为NULL时返回NULL
}
String inputMonth = monthText.toString();
// 获取第二个参数(Long或Int类型)
Object offsetObj = arguments[1].get();
if (offsetObj == null) {
return monthText; // 偏移量为NULL时返回原月份
}
long offset;
if (offsetObj instanceof LongWritable) {
offset = ((LongWritable) offsetObj).get();
} else if (offsetObj instanceof IntWritable) {
offset = ((IntWritable) offsetObj).get();
} else {
throw new UDFArgumentException("第二个参数必须是Long或Int类型");
}
// 计算新月份
try {
// 补全为当月第一天(如"202301" → "20230101")
LocalDate firstDayOfMonth = LocalDate.parse(inputMonth + "01", INIT_DATE_FORMAT);
LocalDate newMonth = firstDayOfMonth.plusMonths(offset);
return new Text(newMonth.format(TARGET_DATE_FORMAT)); // 返回Text类型
} catch (DateTimeParseException e) {
throw new HiveException("日期格式错误,期望yyyyMM,实际输入:" + inputMonth, e);
}
}
@Override
public String getDisplayString(String[] children) {
return String.format("super_add_months(%s, %s)", children[0], children[1]);
}
}
编译打包上面代码并上传到HDFS,以笔者的需求为例,在使用UDF前判断日期范围的sql如下,假设日期字段是period,传参变量为p_period。对比使用UDF和内置函数,显然用自定义函数可以简洁高效的完成相同功能的逻辑,而且UDF还可以实现更复杂的业务需求。
上传jar包到HDFS
hdfs dfs -put SuperAddMonths-1.0.jar /user/hive/function/
# 确认文件是否上传成功
hdfs dfs -ls /user/hive/function/
使用UDF方式
add jar hdfs:///user/hive/function/SuperAddMonths-1.0.jar;
create temporary function super_add_months as 'org.mycode.SuperAddMonths';
-- 测试,查看返回结果是否正确
select super_add_months('202507', -12);
测试没问题就可以改写原有的SQL语句
-- 使用Hive内置函数
select *
from table_xx....
where period <= '${p_period}'
and period > replace(substr(add_months(concat_ws('-', substr('${p_period}', 1, 4), substr('${p_period}', 5, 2), '01'), -12), 1, 7), '-', '')
-- 改写后
select *
from table_xx....
where period <= '${p_period}'
and period > super_add_months('${p_period}', -12)
下面语句可以用来查看函数相关信息,本文就不再赘述。
show functions like '%super%'
describe function super_add_months;
describe function extended super_add_months;
四、前方有坑请注意
1、出现代码运行报错:ClassCastException java.lang.String cannot be cast to org.apache.hadoop.io.Text
解:evaluate应该返回Text对象(与initialize声明的返回类型一致),而不是String。因为String是Java原生类型,而Hive内部使用Writable类型,所以需要将结果包装为Text对象
五、总结
Hive自定义函数是扩展SQL能力的一把利器,掌握这门技巧可以让达到事半功倍的效果。动手实践是掌握UDF开发的关键,不妨从一个小需求开始逐步积累经验!
如果读者遇到其他问题欢迎评论区留言。
参考资料:
- Hive 官方文档:Hive UDF Development
- Java 时间 API:LocalDate 官方文档
Hive自定义函数(UDF)开发和应用流程.18981521的更多相关文章
- hive自定义函数UDF UDTF UDAF
Hive 自定义函数 UDF UDTF UDAF 1.UDF:用户定义(普通)函数,只对单行数值产生作用: UDF只能实现一进一出的操作. 定义udf 计算两个数最小值 public class Mi ...
- Hive自定义函数UDF和UDTF
UDF(user defined functions) 用于处理单行数据,并生成单个数据行. PS: l 一个普通UDF必须继承自“org.apache.hadoop.hive.ql.exec.UDF ...
- Hive 自定义函数 UDF UDAF UDTF
1.UDF:用户定义(普通)函数,只对单行数值产生作用: 继承UDF类,添加方法 evaluate() /** * @function 自定义UDF统计最小值 * @author John * */ ...
- Week08_day01 (Hive 自定义函数 UDF 一个输入,一个输出(最常用))
当我们进入企业就会发现,很多时候,企业的数据都是加密的,我们拿到的数据没办法使用Hive自带的函数去解决,我们就需要自己去定义函数去查看,哈哈,然而企业一般不会将解密的代码给你的,只需要会用,但是我们 ...
- 10_Hive自定义函数UDF
Hive官方的UDF手册地址是:https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF 1.使用内置函数的快捷方法: 创 ...
- 三 Hive 数据处理 自定义函数UDF和Transform
三 Hive 自定义函数UDF和Transform 开篇提示: 快速链接beeline的方式: ./beeline -u jdbc:hive2://hadoop1:10000 -n hadoop 1 ...
- hive自定义函数(UDF)
首先什么是UDF,UDF的全称为user-defined function,用户定义函数,为什么有它的存在呢?有的时候 你要写的查询无法轻松地使用Hive提供的内置函数来表示,通过写UDF,Hive就 ...
- hive自定义函数学习
1介绍 Hive自定义函数包括三种UDF.UDAF.UDTF UDF(User-Defined-Function) 一进一出 UDAF(User- Defined Aggregation Funcat ...
- Hive自定义函数的学习笔记(1)
前言: hive本身提供了丰富的函数集, 有普通函数(求平方sqrt), 聚合函数(求和sum), 以及表生成函数(explode, json_tuple)等等. 但不是所有的业务需求都能涉及和覆盖到 ...
- hive -- 自定义函数和Transform
hive -- 自定义函数和Transform UDF操作单行数据, UDAF:聚合函数,接受多行数据,并产生一个输出数据行 UDTF:操作单个数据 使用udf方法: 第一种: add jar xxx ...
随机推荐
- Vue(六)——条件渲染
Vue--条件渲染 v-if.v-else-if.v-else v-if 指令用于条件性地渲染一块内容,表达式的值为 true --渲染. false--不渲染 v-if.v-else-if.v-el ...
- 零基础搭建AI作曲工具:基于Magenta/TensorFlow的交互式音乐生成系统
引言:当AI遇见莫扎特 "音乐是流动的建筑",当人工智能开始理解音符间的数学规律,音乐创作正经历着前所未有的范式变革.本文将手把手教你构建一套智能作曲系统,不仅能够生成古典钢琴小品 ...
- 同余最短路&转圈背包算法学习笔记(超详细)
一.问题引入 当你想要解决一个完全背包计数问题,但是 \(M\) 的范围太大,那么你就可以使用同余最短路. 二.算法推导过程 首先对于一个完全背包计数问题,我们要知道如果 \(x\) 这个数能凑出来, ...
- docker部署SonarQube流程及相关问题汇总
环境说明: sonarqube版本:10.4.1-community PostgreSql版本:14.1 系统环境:centos7.6(x86_64) 部署流程 1.PostgreSql的安装部署 在 ...
- 堆叠、MLAG、VPC、VSS 技术对比及架构建议
堆叠.MLAG.VPC.VSS 技术对比及架构建议 1. 堆叠(Stacking) 技术实现: 多台物理设备通过专用堆叠线缆(如华为的Stack.华三IRF.思科StackWise)或普通光纤/以太网 ...
- Python 变量作用域 LEGB
回顾 - Decorator 前篇有讲到了, 闭包和装饰器的概念. 闭包就是, 函数内部嵌套函数. 而 装饰器只是闭包的特殊场景而已, 特殊在如果外函数的参数是指向一个, 用来被装饰的函数地址时(不一 ...
- codeup之复制字符串中的元音字母
Description 写一个函数,将一个字符串中的元音字母复制到另一个字符串中.在主函数中输入一个字符串,通过调用该函数,得到一个有该字符串中的元音字母组成的一个字符串,并输出. Input 一个字 ...
- linux下将qt程序打包成appimage程序
linux下将qt程序打包成appimage程序 一.环境准备 1.1下载linuxdeployqt的程序(打包qt程序的工具) https://github.com/probonopd/linuxd ...
- python根据日期、随机数生成编码
import datetime import random import string """ 编码格式:YYYYMMDD 身份证后四位.四位随机数 "& ...
- Java 自定义线程池的线程工厂
本文分享创建线程工厂 ThreadFactory 的三种方式,以方便大家快速创建线程池,并通过线程工厂给每个创建出来的线程设置极富业务含义的名字. 线程池大小考虑因素 由于需要自定义线程池,故 ...