大象教程
首页
Spark
Hadoop
HDFS
MapReduce
Hive
Hive 教程
Hive 教程
Hive 安装(基于Ubuntu系统)
Hive 架构
Hive 内置函数
Hive UDF 简介
Hive DDL 命令
Hive 视图
Hive 索引
Hive Metastore 的三种配置方式
Hive 数据模型
Hive 数据类型
Hive 操作符
Hive SerDe(序列化与反序列化)
Hive 数据分区
Hive 分桶
Hive 分区与分桶的比较
Hive Join 的原理与机制
Hive map Join
Hive bucket map join
#Hive UDF 简介 在 Hive 中,用户可以自定义一些函数,用于扩展 HiveQL 的功能,而这类函数叫做 UDF,也就是用户自定义函数。UDF 分为两大类:UDAF(用户自定义聚合函数)和 UDTF(用户自定义表生成函数)。本节介绍的是比较简单的 UDF 实现—— UDF 和 GenericUDF。 Hive 有两个不同的接口编写 UDF 程序。一个是基础的 UDF 接口,一个是复杂的 GenericUDF 接口。 **org.apache.hadoop.hive.ql. exec.UDF** 基础 UDF 的函数读取和返回基本类型,即 Hadoop 和 Hive 的基本类型。如 Text、IntWritable、LongWritable、DoubleWritable 等。 **org.apache.hadoop.hive.ql.udf.generic.GenericUDF** 复杂的 GenericUDF 可以处理 Map、List、Set 类型。 @Describtion 注解是可选的,用于对函数进行说明,其中的 _FUNC_ 字符串表示函数名,当使用 DESCRIBE FUNCTION 命令时,替换成函数名。 @Describtion 包含三个属性: **name**:用于指定Hive中的函数名。 **value**:用于描述函数的参数。 **extended**:额外的说明,如,给出示例。当使用 DESCRIBE FUNCTION EXTENDED name 的时候打印。而且,Hive 要使用 UDF,需要把 Java 文件编译、打包成 jar 文件,然后将 jar 文件加入到 CLASSPATH 中,最后使用 CREATE FUNCTION 语句定义这个 Java 类的函数: ```hive hive> ADD jar /root/experiment/hive/hive-0.0.1-SNAPSHOT.jar; hive> CREATE TEMPORARY FUNCTION hello AS "edu.wzm.hive. HelloUDF"; hive> DROP TEMPORARY FUNCTION IF EXIST hello; ``` ##UDF 本节采用的数据如下: ```hive hive (mydb)> SELECT * FROM employee; OK John Doe 100000.0 ["Mary Smith","Todd Jones"] {"Federal Taxes":0.2,"State Taxes":0.05,"Insurance":0.1} {"street":"1 Michigan Ave.","city":"Chicago","state":"IL","zip":60600} US CA Mary Smith 80000.0 ["Bill King"] {"Federal Taxes":0.2,"State Taxes":0.05,"Insurance":0.1} {"street":"100 Ontario St.","city":"Chicago","state":"IL","zip":60601} US CA Todd Jones 70000.0 [] {"Federal Taxes":0.15,"State Taxes":0.03,"Insurance":0.1} {"street":"200 Chicago Ave.","city":"Oak Park","state":"IL","zip":60700} US CA Bill King 60000.0 [] {"Federal Taxes":0.15,"State Taxes":0.03,"Insurance":0.1} {"street":"300 Obscure Dr.","city":"Obscuria","state":"IL","zip":60100} US CA Boss Man 200000.0 ["John Doe","Fred Finance"] {"Federal Taxes":0.3,"State Taxes":0.07,"Insurance":0.05} {"street":"1 Pretentious Drive.","city":"Chicago","state":"IL","zip":60500} US CA Fred Finance 150000.0 ["Stacy Accountant"] {"Federal Taxes":0.3,"State Taxes":0.07,"Insurance":0.05} {"street":"2 Pretentious Drive.","city":"Chicago","state":"IL","zip":60500} US CA Stacy Accountant 60000.0 [] {"Federal Taxes":0.15,"State Taxes":0.03,"Insurance":0.1} {"street":"300 Main St.","city":"Naperville","state":"IL","zip":60563} US CA Time taken: 0.093 seconds, Fetched: 7 row(s) hive (mydb)> DESCRIBE employee; OK name string salary float subordinates array
deductions map
address struct
``` 简单UDF的实现很简单,只需要继承 UDF,然后实现 evaluate() 方法就行了。 ```java @Description( name = "hello", value = "_FUNC_(str) - from the input string" + "returns the value that is \"Hello $str\" ", extended = "Example:\n" + " > SELECT _FUNC_(str) FROM src;" ) public class HelloUDF extends UDF{ public String evaluate(String str){ try { return "Hello " + str; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); return "ERROR"; } } } ``` 把 jar 文件添加后,创建函数 hello,然后执行结果如下: ```hive hive (mydb)> SELECT hello(name) FROM employee; OK Hello John Doe Hello Mary Smith Hello Todd Jones Hello Bill King Hello Boss Man Hello Fred Finance Hello Stacy Accountant Time taken: 0.198 seconds, Fetched: 7 row(s) ``` ##GenericUDF GenericUDF 实现比较复杂,需要先继承 GenericUDF。这个 API 需要操作 Object Inspectors,并且要对接收的参数类型和数量进行检查。GenericUDF 需要实现以下三个方法: 这个方法只调用一次,并且在` evaluate() `方法之前调用。该方法接受的参数是一个 ObjectInspectors 数组。该方法检查接受正确的参数类型和参数个数。 `abstract ObjectInspector initialize(ObjectInspector[] arguments);` 这个方法类似 UDF 的 `evaluate()` 方法。它处理真实的参数,并返回最终结果。 `abstract Object evaluate(GenericUDF.DeferredObject[] arguments);` 这个方法用于当实现的 GenericUDF 出错的时候,打印出提示信息。而提示信息就是你实现该方法最后返回的字符串。 `abstract String getDisplayString(String[] children);` 下面是实现 GenericUDF,判断一个数组或列表中是否包含某个元素的例子: ```java class ComplexUDFExample extends GenericUDF { ListObjectInspector listOI; StringObjectInspector elementsOI; StringObjectInspector argOI; @Override public String getDisplayString(String[] arg0) { return "arrayContainsExample()"; // this should probably be better } @Override public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException { if (arguments.length != 2) { throw new UDFArgumentLengthException("arrayContainsExample only takes 2 arguments: List
, T"); } // 1. Check we received the right object types. ObjectInspector a = arguments[0]; ObjectInspector b = arguments[1]; if (!(a instanceof ListObjectInspector) || !(b instanceof StringObjectInspector)) { throw new UDFArgumentException("first argument must be a list / array, second argument must be a string"); } this.listOI = (ListObjectInspector) a; this.elementsOI = (StringObjectInspector) this.listOI.getListElementObjectInspector(); this.argOI = (StringObjectInspector) b; // 2. Check that the list contains strings if(!(listOI.getListElementObjectInspector() instanceof StringObjectInspector)) { throw new UDFArgumentException("first argument must be a list of strings"); } // the return type of our function is a boolean, so we provide the correct object inspector return PrimitiveObjectInspectorFactory.javaBooleanObjectInspector; } @Override public Object evaluate(DeferredObject[] arguments) throws HiveException { // get the list and string from the deferred objects using the object inspectors // List
list = (List
) this.listOI.getList(arguments[0].get()); int elemNum = this.listOI.getListLength(arguments[0].get()); // LazyListObjectInspector llst = (LazyListObjectInspector) arguments[0].get(); // List
lst = llst. LazyString larg = (LazyString) arguments[1].get(); String arg = argOI.getPrimitiveJavaObject(larg); // see if our list contains the value we need for(int i = 0; i < elemNum; i++) { LazyString lelement = (LazyString) this.listOI.getListElement(arguments[0].get(), i); String element = elementsOI.getPrimitiveJavaObject(lelement); if(arg.equals(element)){ return new Boolean(true); } } return new Boolean(false); } } } ``` **注意**:在 Hive-1.0.1 估计之后的版本也是,evaluate() 方法中从 Object Inspectors 取出的值,需要先保存为 Lazy 包中的数据类型(org.apache.hadoop.hive.serde2.lazy),然后才能转换成 Java 的数据类型进行处理。否则会报错,解决方案如下: 在实现GenericUDF时,下面的代码: ```java List
list = (List
) this.listOI.getList(arguments[0].get()); ``` 报错如下: ``` Caused by: java.lang.ClassCastException: org.apache.hadoop.hive.serde2.lazy.LazyString cannot be cast to java.lang.String at edu.wzm.hive.ComplexUDFExample.evaluate(ComplexUDFExample.java:60) at org.apache.hadoop.hive.ql.exec.ExprNodeGenericFuncEvaluator._evaluate(ExprNodeGenericFuncEvaluator.java:185) at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluator.evaluate(ExprNodeEvaluator.java:77) at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluatorHead._evaluate(ExprNodeEvaluatorHead.java:44) at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluator.evaluate(ExprNodeEvaluator.java:77) at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluator.evaluate(ExprNodeEvaluator.java:65) at org.apache.hadoop.hive.ql.exec.SelectOperator.processOp(SelectOperator.java:77) ... 17 more ``` **解决方法**: 在 Hive-1.0.1 实现GenericUDF时,在 `evaluate()` 方法中必须使用 `org.apache.hadoop.hive.serde2.lazy` 包中的 `Lazy` 的数据类型。原因可以在这个帖子:https://issues.apache.org/jira/browse/HIVE-11532。于是,把代码改成如下即可: ```java int elemNum = this.listOI.getListLength(arguments[0].get()); for(int i = 0; i < elemNum; i++) { LazyString lelement = (LazyString) this.listOI.getListElement(arguments[0].get(), i); String element = elementsOI.getPrimitiveJavaObject(lelement); } ``` 把 jar 文件添加后,创建函数 contains,然后执行结果如下: ```hive hive (mydb)> select contains(subordinates, subordinates[0]), subordinates from employee; OK true ["Mary Smith","Todd Jones"] true ["Bill King"] false [] false [] true ["John Doe","Fred Finance"] true ["Stacy Accountant"] false [] Time taken: 0.169 seconds, Fetched: 7 row(s) ``` 现在我们在回头看看 GenericUDF 的模型: 1. 这个 UDF 使用默认的构造方法初始化。 2. initialize() 和一个 Object Inspectors 数组(ListObjectInspector、StringObjectInspector)参数一起被调用。 - 先检查参数个数(2个),和这些参数的类型 - 为 evaluate() 方法保存 Object Inspectors(listOI、argOI、elementsOI) - 返回一个 ObjectInspector(BooleanObjectInspector),且是 Hive 可以读取的方法结果 3. 对于查询的每一行都调用 evaluate()(如,contains(subordinates, subordinates[0])) 取出存储在 Object Inspectors 中的值 处理完 initialize() 方法返回的 Object Inspectors 之后,返回一个值(如,list.contains(elemement) ? true : false)
加我微信交流吧