Hive中的自带函数有时候无法满足我们的需求,因此我们需要自定义hive函数把一些复杂的HQL语句封装起来。我们一般只自定义UDF和UDTF函数,不去自定义UDAF函数是因为自带的UDAF函数以及可以解决大部分问题。
自定义函数需要通过编程的方式来进行,编程步骤:
(1)继承 org.apache.hadoop.hive.ql.UDF(老版本),现在继承GenericUDF
(2)需要实现evaluate函数;evaluate函数支持重载;
(3)在hive的命令行窗口创建函数
a)打包后添加jar
add jar linux_jar_path
b)创建function,
create [temporary] function [dbname.]function_name AS class_name;
(4)在hive的命令行窗口删除函数
Drop [temporary] function [if exists] [dbname.]function_name;
一、UDF函数的自定义
1、我们先来了解一下length()函数底层代码
(1)创建一个java工程,并创建一个lib文件夹
(2)使用hive编程需要的maven依赖
<dependencies>
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>2.3.8</version>
</dependency>
</dependencies>
或者可以将linux里Hive-2.3.8目录下lib里的jar包 copy到IDEA里java项目的lib目录下,然后add as library。
(3)实现求字符串长度的自定义函数(输入一个字符串,返回字符串的长度)
按照上面的编程步骤做,首先继承GenericUDF,接着重写里面的方法(包括initialize初始化方法、evaluate业务逻辑核心方法、getDisplayString不需要关注)
类MyUDF.java:
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.PrimitiveObjectInspectorFactory;
/**
* 函数中可以传入参数,参数可能来自与一张表的字段
* 也可能来自于我们自己写的常量
* 目前我们自定义的函数要求只能是表中的字段 不能是常量
*/
public class MyUDF extends GenericUDF {
/**
* 函数的初始化 一般是用来校验参数的个数和参顺的类型
* @param arguments objectInspector函数的参数、数组
* @return 函数返回值类型
* @throws UDFArgumentException
*/
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
//校验参数的个数是否合法
if (arguments.length !=1){
throw new UDFArgumentException("该函数要求参数的个数只能为1个,你传入了"+arguments.length+"个参数");
}
//检验参数的类型是否合法
//方法要求有一个返回值 返回值是我们的函数的返回值类型(int)
return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
}
/**
* 真正的函数逻辑方法
* @param arguments
* @return
* @throws HiveException
*/
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
int length = arguments[0].get().toString().length();
return length;
}
@Override
public String getDisplayString(String[] children) {
return null;
}
}
(4)打成jar包上传到HDFS文件系统
hdfs dfs -put /opt/data/jars/hf.jar
(5)创建临时函数与开发好的java class关联(若想持久使用就不要加temporary)
hive (default)> create temporary function my_len as "com.ly.MyUDF" using jar "hdfs://ip地址:9000/hf.jar";
如果报错类没有被找到,那么使用如下命令清空jar包信息,重新登录Hive
hive> list jars;
hive> delete jar;
hive> quit;
(6)接着查看关联成功的functions
hive (default)>show functions;
hive (default)>desc function myudf;
(7)然后我们就可以使用自定义的UDF函数啦
select myudf(name) from studentscore; 计算属性name其中值的长度
试错:
【注意事项】(最好用临时函数)
临时函数和永久函数的区别:
- 临时函数在当前hive命令行关闭后就不存在了
- 永久函数命令行关闭还存在
- 临时函数可以使用show function找到,但是永久函数无法使用该命令查询
4、hive中自定义的函数和数据库是挂钩的,在某一个数据库创建的函数别的数据无法使用,除非你在这个数据库下再次创建一个函数。
二、UDTF函数的自定义
自定义UDTF函数的步骤和UDF一样。
要求:自定义一个 UDTF 实现将一个任意分割符的字符串切割成独立的单词
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import java.util.ArrayList;
import java.util.List;
public class MyUDTF extends GenericUDTF {
//每一行的数据 数据可能是一列 也可能是多列
private ArrayList<String> outList = new ArrayList<>();
@Override
public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
//1.定义输出数据的列名和类型
List<String> fieldNames = new ArrayList<>();
List<ObjectInspector> fieldOIs = new ArrayList<>();
//2.添加输出数据的列名和类型
fieldNames.add("lineToWord");
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames,
fieldOIs);
}
@Override
public void process(Object[] args) throws HiveException {
//1.获取原始数据
String arg = args[0].toString();
//2.获取数据传入的第二个参数,此处为分隔符
String splitKey = args[1].toString();
//3.将原始数据按照传入的分隔符进行切分
String[] fields = arg.split(splitKey);
//4.遍历切分后的结果,并写出
for (String field : fields) {
//集合为复用的,首先清空集合
outList.clear();
//将每一个单词添加至集合
outList.add(field);
//将集合内容写出
forward(outList);
}
}
@Override
public void close() throws HiveException {
}
}