Hive 有丰富的内置函数(Built-in Functions),方便数据处理与数据分析等。即便如此,内置函数有时候还是无法满足需求,这时就需要自定义函数(User-Defined Functions , UDF)来扩展 Hive 函数库,实现用户想要的功能。

0x00 自定义 UDF 开发

编写 UDF 需要下面两个步骤:

  • 继承 org.apache.hadoop.hive.ql.exec.UDF
  • 实现 evaluate 函数,这个函数必须要有返回值,不能设置为 void。同时建议使用 mapreduce 编程模型中的数据类型( Text, IntWritable 等),因为 SQL 语句会被转换为 mapreduce 任务;

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.data.hive;

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.hive.ql.exec.Description;
import java.util.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;

@Description(
name = "calc_remain",
value = "_FUNC_(active_list, active_list_date, base_date, remain) - Calculate remain_N based on a date.",
extended = "Example:\n" +
"select calc_remain('1,0,1,0,1,1,0,1','2019-03-15','2019-03-10',3);\n")
public class CalcRemain extends UDF {

private interface Status {
String UnActive = "0";
String Active = "1";
}

public int evaluate(String strActiveList, String strListDate, String strBaseDate, int intRemain) {
if (IsEmpty(strActiveList) || IsEmpty(strListDate)) {
return 0;
}

int intOuput = 0;

Date dtBase = StringToDate(strBaseDate);
Date dtLast = StringToDate(strListDate);

long lngDiffDays = (dtLast.getTime() - dtBase.getTime()) / (24*3600*1000);
if (intRemain < 0 || lngDiffDays < 1 || lngDiffDays < intRemain) {
return 0;
}

int intDiffDays = (int)lngDiffDays;
int lenActiveList = strActiveList.length();

String[] arrActiveList = strActiveList
.substring(lenActiveList-intDiffDays*2-1, lenActiveList)
.split(",");

if (arrActiveList[0].equals(Status.UnActive)) {
return 0;
} else if (arrActiveList[intRemain].equals(Status.Active)) {
intOuput = 1;
} else {
intOuput = 0;
}

return intOuput;
}

private Date StringToDate(String strTime) {
Date dtOutput = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
dtOutput = sdf.parse(strTime);
} catch (ParseException e) {
e.printStackTrace();
}
return dtOutput;
}

private boolean IsEmpty (String strArgument) {
if (strArgument == null || strArgument.length() <= 0) {
return true;
} else {
return false;
}
}
}

0x01 自定义 UDF 部署方式

官方提供了两种部署 UDF 的方式:

  • 临时部署(Temporary Functions)
  • 永久部署(Permanent Functions)

两者的区别在于:临时部署的方式,只会在当前 Session 下有效并可用;永久部署的方式,在部署成功后任何一个 Hive 客户端(重新启动的 Hive 客户端,已经启动的客户端需要重新加载)都可以使用。

(1) 临时部署

这个是最常见的 Hive 使用方式,通过 hive 命令来完成 UDF 的部署;

1
2
hive> add jar /path/to/local.jar; 
hive> create temporary function calc_remain as 'com.data.hive.CalcRemain';

建议函数名使用 下划线命名法(全部小写字母)。

(2) 永久部署

这种方式是 hive-0.13 版本以后开始支持的注册方法;

1
2
3
hive> create function udf.calc_remain 
hive> as 'com.data.hive.CalcRemain'
hive> using jar 'hdfs:///path/to/hdfs.jar';

需要注意的是:函数名称前面一定要带上数据库名称。

0x02 函数相关的 HQL 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 查看所有函数(内置函数+自定义函数)
show functions;
-- 查看某个函数的使用说明
describe function function_name;
-- 创建临时自定义函数
create temporary function function_name as class_name;
-- 删除临时自定义函数
drop temporary function [if exists] function_name;
-- 创建永久自定义函数
create function [db_name.]function_name as class_name
[using jar|file|archive 'file_uri' [, jar|file|archive 'file_uri'] ];
-- 删除永久自定义函数
drop function [if exists] function_name;
-- 重载函数
reload function;

0x03 扩展与延伸

Hive 自定义函数的扩展,不仅有 UDF(User-Defined Functions),还有 UDAF(User-Defined Aggregate Functions)和 UDTF(User-Defined Table-Generating Functions),详情请参考官方说明。

另外,如果数据处理在函数级别不能解决,还可以借助 TRANSFORM 自定义 Map 和 Reduce 函数,或者编写 MapReduce 程序。

参考文献

Creating Custom UDFs
Java编写Hive的UDF
Hive UDF 部署方式小结
Writing GenericUDAFs
Hive’s Transform functionality