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> 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 (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<string>
deductions map<string,float>
address struct<street:string,city:string,state:string,zip:int>
简单UDF的实现很简单,只需要继承 UDF,然后实现 evaluate() 方法就行了。
@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 (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,判断一个数组或列表中是否包含某个元素的例子:
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>, 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<String> list = (List<String>) this.listOI.getList(arguments[0].get());
int elemNum = this.listOI.getListLength(arguments[0].get());
// LazyListObjectInspector llst = (LazyListObjectInspector) arguments[0].get();
// List<String> 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时,下面的代码:
List<String> list = (List<String>) 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。于是,把代码改成如下即可:
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 (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 的模型:
- 这个 UDF 使用默认的构造方法初始化。
- initialize() 和一个 Object Inspectors 数组(ListObjectInspector、StringObjectInspector)参数一起被调用。
- 先检查参数个数(2个),和这些参数的类型
- 为 evaluate() 方法保存 Object Inspectors(listOI、argOI、elementsOI)
- 返回一个 ObjectInspector(BooleanObjectInspector),且是 Hive 可以读取的方法结果
- 对于查询的每一行都调用 evaluate()(如,contains(subordinates, subordinates[0]))
取出存储在 Object Inspectors 中的值
处理完 initialize() 方法返回的 Object Inspectors 之后,返回一个值(如,list.contains(elemement) ? true : false)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论