返回介绍

Hive UDF 简介

发布于 2024-06-23 16:10:22 字数 16793 浏览 0 评论 0 收藏 0

在 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 类的函数:

  1. hive> ADD jar /root/experiment/hive/hive-0.0.1-SNAPSHOT.jar;
  2. hive> CREATE TEMPORARY FUNCTION hello AS "edu.wzm.hive. HelloUDF";
  3. hive> DROP TEMPORARY FUNCTION IF EXIST hello;

UDF

本节采用的数据如下:

  1. hive (mydb)> SELECT * FROM employee;
  2. OK
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. Time taken: 0.093 seconds, Fetched: 7 row(s)
  11. hive (mydb)> DESCRIBE employee;
  12. OK
  13. name string
  14. salary float
  15. subordinates array<string>
  16. deductions map<string,float>
  17. address struct<street:string,city:string,state:string,zip:int>

简单UDF的实现很简单,只需要继承 UDF,然后实现 evaluate() 方法就行了。

  1. @Description(
  2. name = "hello",
  3. value = "_FUNC_(str) - from the input string"
  4. + "returns the value that is \"Hello $str\" ",
  5. extended = "Example:\n"
  6. + " > SELECT _FUNC_(str) FROM src;"
  7. )
  8. public class HelloUDF extends UDF{
  9. public String evaluate(String str){
  10. try {
  11. return "Hello " + str;
  12. } catch (Exception e) {
  13. // TODO: handle exception
  14. e.printStackTrace();
  15. return "ERROR";
  16. }
  17. }
  18. }

把 jar 文件添加后,创建函数 hello,然后执行结果如下:

  1. hive (mydb)> SELECT hello(name) FROM employee;
  2. OK
  3. Hello John Doe
  4. Hello Mary Smith
  5. Hello Todd Jones
  6. Hello Bill King
  7. Hello Boss Man
  8. Hello Fred Finance
  9. Hello Stacy Accountant
  10. 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,判断一个数组或列表中是否包含某个元素的例子:

  1. class ComplexUDFExample extends GenericUDF {
  2. ListObjectInspector listOI;
  3. StringObjectInspector elementsOI;
  4. StringObjectInspector argOI;
  5. @Override
  6. public String getDisplayString(String[] arg0) {
  7. return "arrayContainsExample()"; // this should probably be better
  8. }
  9. @Override
  10. public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
  11. if (arguments.length != 2) {
  12. throw new UDFArgumentLengthException("arrayContainsExample only takes 2 arguments: List<T>, T");
  13. }
  14. // 1. Check we received the right object types.
  15. ObjectInspector a = arguments[0];
  16. ObjectInspector b = arguments[1];
  17. if (!(a instanceof ListObjectInspector) || !(b instanceof StringObjectInspector)) {
  18. throw new UDFArgumentException("first argument must be a list / array, second argument must be a string");
  19. }
  20. this.listOI = (ListObjectInspector) a;
  21. this.elementsOI = (StringObjectInspector) this.listOI.getListElementObjectInspector();
  22. this.argOI = (StringObjectInspector) b;
  23. // 2. Check that the list contains strings
  24. if(!(listOI.getListElementObjectInspector() instanceof StringObjectInspector)) {
  25. throw new UDFArgumentException("first argument must be a list of strings");
  26. }
  27. // the return type of our function is a boolean, so we provide the correct object inspector
  28. return PrimitiveObjectInspectorFactory.javaBooleanObjectInspector;
  29. }
  30. @Override
  31. public Object evaluate(DeferredObject[] arguments) throws HiveException {
  32. // get the list and string from the deferred objects using the object inspectors
  33. // List<String> list = (List<String>) this.listOI.getList(arguments[0].get());
  34. int elemNum = this.listOI.getListLength(arguments[0].get());
  35. // LazyListObjectInspector llst = (LazyListObjectInspector) arguments[0].get();
  36. // List<String> lst = llst.
  37. LazyString larg = (LazyString) arguments[1].get();
  38. String arg = argOI.getPrimitiveJavaObject(larg);
  39. // see if our list contains the value we need
  40. for(int i = 0; i < elemNum; i++) {
  41. LazyString lelement = (LazyString) this.listOI.getListElement(arguments[0].get(), i);
  42. String element = elementsOI.getPrimitiveJavaObject(lelement);
  43. if(arg.equals(element)){
  44. return new Boolean(true);
  45. }
  46. }
  47. return new Boolean(false);
  48. }
  49. }
  50. }

注意:在 Hive-1.0.1 估计之后的版本也是,evaluate() 方法中从 Object Inspectors 取出的值,需要先保存为 Lazy 包中的数据类型(org.apache.hadoop.hive.serde2.lazy),然后才能转换成 Java 的数据类型进行处理。否则会报错,解决方案如下:

在实现GenericUDF时,下面的代码:

  1. List<String> list = (List<String>) this.listOI.getList(arguments[0].get());

报错如下:

  1. Caused by: java.lang.ClassCastException: org.apache.hadoop.hive.serde2.lazy.LazyString cannot be cast to java.lang.String
  2. at edu.wzm.hive.ComplexUDFExample.evaluate(ComplexUDFExample.java:60)
  3. at org.apache.hadoop.hive.ql.exec.ExprNodeGenericFuncEvaluator._evaluate(ExprNodeGenericFuncEvaluator.java:185)
  4. at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluator.evaluate(ExprNodeEvaluator.java:77)
  5. at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluatorHead._evaluate(ExprNodeEvaluatorHead.java:44)
  6. at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluator.evaluate(ExprNodeEvaluator.java:77)
  7. at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluator.evaluate(ExprNodeEvaluator.java:65)
  8. at org.apache.hadoop.hive.ql.exec.SelectOperator.processOp(SelectOperator.java:77)
  9. ... 17 more

解决方法
在 Hive-1.0.1 实现GenericUDF时,在 evaluate() 方法中必须使用 org.apache.hadoop.hive.serde2.lazy 包中的 Lazy 的数据类型。原因可以在这个帖子:https://issues.apache.org/jira/browse/HIVE-11532。于是,把代码改成如下即可:

  1. int elemNum = this.listOI.getListLength(arguments[0].get());
  2. for(int i = 0; i < elemNum; i++) {
  3. LazyString lelement = (LazyString) this.listOI.getListElement(arguments[0].get(), i);
  4. String element = elementsOI.getPrimitiveJavaObject(lelement);
  5. }

把 jar 文件添加后,创建函数 contains,然后执行结果如下:

  1. hive (mydb)> select contains(subordinates, subordinates[0]), subordinates from employee;
  2. OK
  3. true ["Mary Smith","Todd Jones"]
  4. true ["Bill King"]
  5. false []
  6. false []
  7. true ["John Doe","Fred Finance"]
  8. true ["Stacy Accountant"]
  9. false []
  10. 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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文