1. Hive自带了一些函数,比如:max/min等,但是数量有限,自己可以通过自定义 UDF来方便的扩展。
  2. 当 Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数。

1. 自定义函数种类

虽然hive中为我们提供了很多的内置函数,但是在实际工作中,有些情况下hive提供的内置函数无法满足我们的需求,就需要我们自己来手动编写,所以就有了自定义函数 UDF。

UDF分为三种,分别如下:

  1. UDF(User-Defined-Function):

一进一出(输入一行,输出一行),
输入一行数据, 输出一行数据; 比如:upper()、lowser()等。

  1. UDAF(User-Defined Aggregation Funcation)

多进一出(输入多行,输出一行),
输入多行数据, 聚合成一行数据, 比如:avg()、sum()等。

  1. UDTF(User-Defined Table-Generating Functions)

一进多出(输入一行,输出多行),
比如:collect_set()、collect_list()等。

官方文档:https://cwiki.apache.org/confluence/display/Hive/HivePlugins

注意: 使用自定义函数需要引入hive-exec的依赖

<dependency>
    <groupId>org.apache.hive</groupId>
    <artifactId>hive-exec</artifactId>
    <version>3.2.1</version>
</dependency>

2. 编写自定义UDF函数

UDF, 输入数据是一行数据, 输出数据也是一行数据,

在此, 我们编写UDF实现给定字符串, 返回字符串的长度, my_len();

2.1 编写UDF的步骤

  1. 继承org.apache.hadoop.hive.ql.udf.generic.*GenericUDF*
  • 之前的版本是继承UDF类, 这个类已经过时了.
  1. 从GenericUDF, 继承了三个方法, 进行重写
  1. , 设置在explain执行计划中显示的语句.
  1. 打包, 本地部署就放到本地任意目录, 非本地模式, 就放入到共享存储, 如HDFS上。
  2. 添加Jar包, 创建函数
  1. 在hive的命令行窗口创建函数
  1. 添加jar
  1. add jar /路径
  1. 创建function
  1. create temporary function 函数名 as "自定义udf类的全类名"
  2. 临时函数用于解决一些临时特殊的业务需求而开发的函数,hive中注册的临时函数只在当前会话可用,注册函数的时候用temporary关键字声名。
  1. 使用函数
  1. 在hive的命令行窗口使用函数;

  1. 删除函数
  1. Drop [temporary] function [if exists] [dbname.]function_name;

2.2 参考代码

//import org.apache.hadoop.hive.ql.exec.UDF;   已经过期
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 GetLen extends GenericUDF {
    @Override
    //校验数据参数个数等初始化操作
    public ObjectInspector initialize(ObjectInspector[] arguements) throws UDFArgumentException {
        if(arguements.length != 1){
            throw new UDFArgumentException("错误, 输入参数不为1 !");
        }
        //函数本身返回值为int,需要返回int类型的鉴别器对象, 记不住的话就去GenericUDF中找一个方法参考下写法即可
        return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
    }
    
    /**
     *函数的逻辑处理
     * @param arguements 函数的参数
     * @return 返回值
     * @throws HiveException
     */
    @Override
    public Object evaluate(DeferredObject[] arguements) throws HiveException {
        //1. 取出输入的参数值, 转为string
        String str = arguements[0].get().toString();

        //返回长度, 为了避免null 的string还要进行一个判断
        return str == null ? 0 : str.length();
    }

    @Override
    public String getDisplayString(String[] strings) {
        return null;
    }
}

3. 编写自定义UDTF函数

UDTF, 输入一行数据, 输出多行数据

在此, 我们实现一个字符串切割函数, 给定一行字符串, 按照指定的分隔符进行切分, 并输出。

3.1 编写UDTF的步骤

  1. 继承org.apache.hadoop.hive.ql.udf.generic.*GenericUDTF*
  1. 之前的版本是继承UDTF类, 这个类已经过时了.
  1. 从GenericUDTF, 继承了三个方法, 进行重写

  2. 打包, 本地部署就放到本地任意目录, 非本地模式, 就放入到共享存储, 如HDFS上。
  3. 添加Jar包, 创建函数
  1. 在hive的命令行窗口创建函数
  1. 添加jar
  1. add jar /路径
  1. 创建function
  1. create temporary function 函数名 as "自定义udf类的全类名"
  2. 临时函数用于解决一些临时特殊的业务需求而开发的函数,hive中注册的临时函数只在当前会话可用,注册函数的时候用temporary关键字声名。
  1. 使用函数
  1. 在hive的命令行窗口使用函数;

  1. 删除函数
  1. Drop [temporary] function [if exists] [dbname.]function_name;

3.2 参考代码

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.PrimaryKeyInfo;
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 SplitString extends GenericUDTF {

    //全局变量, 用于存储要写出个每一个单词
    private List<String> outputList = null;
    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        //SQL 查询输出数据的默认列名
        List<String> fieldsName = new ArrayList<>();
        fieldsName.add("word");
        //输出数据的类型
        List<ObjectInspector> fieldsOIs = new ArrayList<>();
        fieldsOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

        //最终返回值
        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldsName, fieldsOIs);
    }

    //处理输入数据的方法
    @Override
    public void process(Object[] arguments) throws HiveException {
        //1. 获取第一个参数, 也就是待分割字符串
        String targetString = arguments[0].toString();
        //2. 获取第二个参数, 也就是切分字符
        String splitKey = arguments[1].toString();
        //3. 执行分割
        String[] strings = targetString.split(splitKey);

        //一进多出, 就像是使用map或者reduce过程的写出  context.write(key,value)
        // 只不过hive中是forward方法
        for(String str : strings){
            //要输出的是一个个的单词, 每次遍历都把这个单词放入list
            //然后使用forward方法写出
            outputList.clear();
            outputList.add(str);
            forward(outputList);
        }

    }

    //收尾方法
    @Override
    public void close() throws HiveException {

    }
}