hive-6(UDF、UDAF、UDTF)

hive-6

Hive中内置了很多的函数,包含了日常工作需求的字符串处理、日期时间处理等常用函数,在Hive CLI界面中,可以使用show functions查看全部可用函数,要查看某个函数的作用和用法,可以使用desc function <function_name>指令:
在这里插入图片描述
当内置函数无法满足我们的需求时,Hive提供了可供用户自定义函数的接口,通过实现指定接口,可以创建实现自定义功能的函数。

自定义函数根据输入输出的方式,大致分为三类

函数类型描述
UDF单行进单行出,每一行执行产出一行的结果
UDAF多行进单行出,对多行数据统计结果
UDTF单行进多行出,一行数据产生多行结果

创建自定义函数需要导入的依赖:

<!--根据实际使用版本调整-->
<dependency>
  <groupId>org.apache.hadoop</groupId>
  <artifactId>hadoop-client</artifactId>
  <version>2.6.0</version>
</dependency>
<dependency>
  <groupId>org.apache.hive</groupId>
  <artifactId>hive-exec</artifactId>
  <version>1.1.0</version>
</dependency>

UDF

自定义的UDF类需要继承org.apache.hadoop.hive.ql.exec.UDF抽象类,并定义一个evaluate方法,该方法的参数列表就是输入的数据(只能有一行,但是可以有多列),返回值就是我们需要的结果

需要注意的是,该方法并不是重写或实现方法,但名称必选是evaluate

案例需求:格式化姓名,将其改为首字母大写,其余字母全部小写的格式:

import org.apache.hadoop.hive.ql.exec.UDF;

public class FormatName extends UDF {
    public String evaluate(String input) {
        String lower = input.toLowerCase();
        char c = lower.charAt(0);
        return lower.replaceFirst(String.valueOf(c), String.valueOf((char) (c - 32)));
    }
}

实现代码编写完成后,使用maven工具打包,并上传

接下来,需要添加包含UDF函数的JAR包:add jar <path>;
在这里插入图片描述
添加完成后,就可以创建函数了:create [temporary] function <fun_name> as <full_name>;

  1. 指定函数名称
  2. 传入自定义UDF类的全类名

在这里插入图片描述
此时再使用show functions指令就可以看到新添加的函数了
在这里插入图片描述
试用函数:
在这里插入图片描述

UDAF

定义UDAF函数大致需要两部分,第一部分是resolver类,resolver类需要继承org.apache.hadoop.hive.ql.udf.GenericUDAFResolver2抽象类,主要用于对传入的参数进行检查,包括参数的个数和参数的类型,下面是resolver类的基本结构

public class GenericUDAFHistogramNumeric extends AbstractGenericUDAFResolver {
	//LOG对象可以采集过程中的WARN和ERROR信息,并反馈到Hive Log中
  static final Log LOG = LogFactory.getLog(GenericUDAFHistogramNumeric.class.getName());
 
  @Override
  public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) throws SemanticException {
    // 在这里检查参数的类型和数量
 
    return new GenericUDAFHistogramNumericEvaluator();
  }
 
  public static class GenericUDAFHistogramNumericEvaluator extends GenericUDAFEvaluator {
    // 在这里编写UDAF的逻辑代码
  }
}

第二部分是evaluator类。evaluator类定义在resolver类的内部,并继承org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator抽象类,在本类中编写聚合的逻辑代码。

evaluator的基本结构:

  public static class GenericUDAFHistogramNumericEvaluator extends GenericUDAFEvaluator {
 
    // 对于PARTIAL1和COMPLETE: 传入数据类型的ObejctInspector
    private PrimitiveObjectInspector inputOI;
    private PrimitiveObjectInspector nbinsOI;
 
    // 对于PARTIAL2和FINAL: 部分聚合结果的ObjectInspectors
    private StandardListObjectInspector loi;
 
 
    @Override
    public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
      super.init(m, parameters);
    }
 
    @Override
    public Object terminatePartial(AggregationBuffer agg) throws HiveException {
    }
 
    @Override
    public Object terminate(AggregationBuffer agg) throws HiveException {
    }
 
    @Override
    public void merge(AggregationBuffer agg, Object partial) throws HiveException {
    }
 
    @Override
    public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
    }
 
    // Aggregation buffer definition and manipulation methods
    static class StdAgg implements AggregationBuffer {
    };
 
    @Override
    public AggregationBuffer getNewAggregationBuffer() throws HiveException {
    }
 
    @Override
    public void reset(AggregationBuffer agg) throws HiveException {
    }   
  }

要理解上面的代码结构,首先要了解evaluator中各个方法是做什么的:

方法作用
initHive调用本方法来创建evaluator类实例
getNewAggregationBuffer获得临时存储中间结果的对象
iterate每传入一行数据,使用本方法的逻辑计算,将本行内容加载到临时缓存中
terminatePartial获取当前缓存的结果,需要注意,返回的类型只能是java基本类型、数组、包装类、Hadoop Writable类型和集合类(List、Map)
merge合并两个terminatePartial方法获得的中间结果
terminate获取要输出的最终结果

接下来,看一下聚合的几个过程模式ModeMode是一个定义在GenericUDAFEvaluator类中的枚举类

  /**
   * Mode.
   *
   */
  public static enum Mode {
    /**
     * PARTIAL1: from original data to partial aggregation data: iterate() and
     * terminatePartial() will be called.
     */
    PARTIAL1,
        /**
     * PARTIAL2: from partial aggregation data to partial aggregation data:
     * merge() and terminatePartial() will be called.
     */
    PARTIAL2,
        /**
     * FINAL: from partial aggregation to full aggregation: merge() and
     * terminate() will be called.
     */
    FINAL,
        /**
     * COMPLETE: from original data directly to full aggregation: iterate() and
     * terminate() will be called.
     */
    COMPLETE
  };

一共四种模式,文档中也给出了各模式会调用的方法

  1. PARTIAL1:处理输入数据后合并为中间结果,可以理解为MR中的Map阶段
  2. PARTIAL2:合并中间结果再以最终结果的形式输入,可以理解为MR中的Shuffle-Combine阶段
  3. FINAL:合并结果并输出,可以理解为MR中的Reduce阶段
  4. COMPLETE:处理输入数据后直接输出,可以理解为没有Reduce的Map

下面是自定义avg函数的实现:

import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
import org.apache.hadoop.hive.serde2.objectinspector.*;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.DoubleObjectInspector;
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.PrimitiveObjectInspectorUtils;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.LongWritable;

import java.util.ArrayList;
import java.util.List;

public class ThirdAggregation extends AbstractGenericUDAFResolver {
    @Override
    public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) throws SemanticException {
    	// 在此处添加参数判断
        return new MyAvgEvaluator();
    }

    public static class MyAvgEvaluator extends GenericUDAFEvaluator {
    	// 存储初始输入数据的格式
        PrimitiveObjectInspector inputOI;

		// 存储临时缓存的格式,用于merge过程取出数值
        StructObjectInspector soi;
        StructField sumSF;
        StructField countSF;
        DoubleObjectInspector sumOI;
        LongObjectInspector countOI;

		// 保存临时结果
        Object[] tmpResult;

        @Override
        public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
            super.init(m, parameters);
            if (m == Mode.PARTIAL1 || m == Mode.COMPLETE)
                inputOI = (PrimitiveObjectInspector) parameters[0];
            else {
                soi = (StructObjectInspector) parameters[0];
                sumSF = soi.getStructFieldRef("sum");
                countSF = soi.getStructFieldRef("count");
                sumOI = (DoubleObjectInspector) sumSF.getFieldObjectInspector();
                countOI = (LongObjectInspector) countSF.getFieldObjectInspector();
            }
            if (m == Mode.PARTIAL1 || m == Mode.PARTIAL2) {
                tmpResult = new Object[2];
                List<String> name = new ArrayList<>();
                name.add("sum");
                name.add("count");
                List<ObjectInspector> soi = new ArrayList<>();
                soi.add(PrimitiveObjectInspectorFactory.writableDoubleObjectInspector);
                soi.add(PrimitiveObjectInspectorFactory.writableLongObjectInspector);
                return ObjectInspectorFactory.getStandardStructObjectInspector(name, soi);
            } else
                return PrimitiveObjectInspectorFactory.writableDoubleObjectInspector;
        }

        @Override
        public AggregationBuffer getNewAggregationBuffer() throws HiveException {
        	// 新建一个临时缓存对象并返回
            TempBuffer b = new TempBuffer();
            reset(b);
            return b;
        }

        @Override
        public void reset(AggregationBuffer agg) throws HiveException {
        	// 将传入的临时缓存对象的sum和count都重置为0
            TempBuffer b = (TempBuffer) agg;
            b.sum = 0;
            b.count = 0;
        }

        @Override
        public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
            TempBuffer b = (TempBuffer) agg;
            // 取出当前行传入的数值
            double v = PrimitiveObjectInspectorUtils.getDouble(parameters[0], inputOI);
            b.sum += v;
            b.count += 1;
        }

        @Override
        public Object terminatePartial(AggregationBuffer agg) throws HiveException {
        	// 不可以直接返回TempBuffer对象,要和init中指定的类型相匹配
            TempBuffer b = (TempBuffer) agg;
            tmpResult[0] = new DoubleWritable(b.sum);
            tmpResult[1] = new LongWritable(b.count);
            return tmpResult;
        }

        @Override
        public void merge(AggregationBuffer agg, Object partial) throws HiveException {
            TempBuffer b = (TempBuffer) agg;
            // 取出另一个缓存分区中的数据,两者相加
            double sum = sumOI.get(soi.getStructFieldData(partial, sumSF));
            long count = countOI.get(soi.getStructFieldData(partial, countSF));
            b.sum += sum;
            b.count += count;
        }

        @Override
        public Object terminate(AggregationBuffer agg) throws HiveException {
        	// 返回最终结果,这里的结果也要和init中指定的类型匹配,否则会出现ClassCastException
            TempBuffer b = (TempBuffer) agg;
            return new DoubleWritable(b.sum / b.count);
        }
		
		// 临时缓存结构
        static class TempBuffer implements AggregationBuffer {
            double sum;
            long count;
        }
    }
}

上述代码中的init()方法,可以结合源码中的文档理解:

  /**
   * Initialize the evaluator.
   * 
   * @param m
   *          The mode of aggregation.
   * @param parameters
   *          The ObjectInspector for the parameters: In PARTIAL1 and COMPLETE
   *          mode, the parameters are original data; In PARTIAL2 and FINAL
   *          mode, the parameters are just partial aggregations (in that case,
   *          the array will always have a single element).
   * @return The ObjectInspector for the return value. In PARTIAL1 and PARTIAL2
   *         mode, the ObjectInspector for the return value of
   *         terminatePartial() call; In FINAL and COMPLETE mode, the
   *         ObjectInspector for the return value of terminate() call.
   * 
   *         NOTE: We need ObjectInspector[] (in addition to the TypeInfo[] in
   *         GenericUDAFResolver) for 2 reasons: 1. ObjectInspector contains
   *         more information than TypeInfo; and GenericUDAFEvaluator.init at
   *         execution time. 2. We call GenericUDAFResolver.getEvaluator at
   *         compilation time,
   */

对于PARTIAL1 和COMPLETE模式,parameter参数表示传入的原始数据;对于PARTIAL2 和FINAL模式,parameter参数表示临时的聚合结果(对应代码中的第一个if…else)

对于PARTIAL1 和PARTIAL2模式,应当返回terminatePartial()方法的返回类型;对于FINAL和COMPLETE模式,应当返回terminate()方法的返回类型(对应代码中第二个if…else)

完成代码后,打包上传并测试,创建函数时,要指定AbstractGenericUDAFResolver子类全路径,其余过程与普通UDF相同

hive> select shop_id,myavg(month_sales) from ex_spu group by shop_id;

在这里插入图片描述
以上实现为了能清楚的显示逻辑,删去了日志输出和各种类型判断,在正常使用时,一定要在这个的基础上加上对参数个数、类型、数值的判断

总结下,能明白几个函数的作用的话,udaf逻辑还是比较简单的,但是需要注意,hive 使用 ObjectInspector来分析行对象的内部结构以及各个列的结构,因此各个方法内数据的读取一定要使用对应的格式来读取;另一点就是UDAF中会大量使用强制类型转换,一定要清楚被转换的对象是由那个方法传递过来的;以上两点是使用udaf报ClassCastException的主要原因。

当异常类型为NullPointerException时,可以优先排查定义的几个成员属性有没有在init方法中做初始化

UDTF

UDTF与UDAF相反,每次传入一行数据,结果输出多行数据。自定义的UDTF类需要继承org.apache.hadoop.hive.ql.udf.generic.GenericUDTF类。

UDTF的实现相比UDAF要简单很多,下面我们直接实现一个自定义的UDTF案例:

案例数据:

hive> drop table if exists info;
hive> create table info(id int,f_info string,s_info string);
hive> insert into info values(1,"a,b,c","1,2,3") (2,"d,e,f","4,5"),(3,"g,h,i","6,7,8,9");

在这里插入图片描述
可以看到第二列和第三列都是由逗号分隔的多个元素组成,下面我们要输入这两列数据,取两个数据集合的笛卡尔积,每个结果输出一行。

实现代码:

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 CrossExplode extends GenericUDTF {
    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
    	// 列名集合,因为输入两列就添加两个元素
        List<String> name = new ArrayList<>();
        name.add("first");
        name.add("second");
        // 列类型集合,和上面的列名集合对应
        List<ObjectInspector> oi = new ArrayList<>();
        oi.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        oi.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        // 如果只有一列输入可以直接返回对应的类型,这里是两列的复合类型
        return ObjectInspectorFactory.getStandardStructObjectInspector(name, oi);
    }

    @Override
    public void process(Object[] args) throws HiveException {
    	// 这里默认输入的两列都至少有一个元素,如果要兼顾含有null的情况,就要对args[n]进行判断
        String a1 = args[0].toString(); // 获取输入的第一列数据
        String[] arr1 = a1.split(",");
        String[] arr2 = args[1].toString().split(","); // 获取输入的第二列数据
        String[] res = new String[2]; // 用于存储返回的结果,我们要返回两列,所以用数组表示
        for (String value : arr1) { // 双重循环,取出所有组合并返回
            res[0] = value;
            for (String s : arr2) {
                res[1] = s;
                forward(res); // forward每调用一次就输出一行结果
            }
        }
    }

    @Override
    public void close() throws HiveException {
		// 释放资源,或者在全部forward执行完成后需要追加输出的,在这个位置定义
    }
}

代码定义完成,接下来一样的打包上传操作,测试下结果:

hive> add jar /opt/udf-1.0-SNAPSHOT.jar;
hive> create temporary function cre as 'user_defined_functions.CrossExplode';

有两种使用方式:

第一种:直接在select语句中使用:

hive> select cre(f_info,s_info) from info;

输出结果:
在这里插入图片描述
这种不能搭配其他列的数值

第二种:在侧视图中使用:

hive> select id,c1,c2 from info lateral view cre(f_info,s_info) a as c1,c2;

输出结果:
在这里插入图片描述
自定义的函数当然也是可以查看执行计划的,我们看下上面两种语句的执行计划:

第一种:
在这里插入图片描述
第二种:
在这里插入图片描述
根据实际需求,灵活选择

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值