UDF样例之通过日期计算其星座

  使用配置
内容纲要

概要描述


UDF(User-Defined Functions)即是用户定义的hive函数。hive自带的函数并不能完全满足业务需求,这时就需要我们自定义函数了。

用户自定义函数(UDF)是一个允许用户扩展HQL的强大的功能。正如我们看到的,用户使用Java进行编码,一旦将用户自定义函数加入到用户会话中,它们就将和内置的函数一样使用,甚至可以提供联机帮助。Hive具有多种类型的用户自定义函数,每一种都会针对输入数据执行特定“一类”的转换过程。

在编写自定义UDF之前,我们先熟悉一下Hive中自带哪些UDF。可以通过SHOW FUNCTIONS命令列举出当前会话中所加载的所有函数名称,其中包括内置的和用户加载进来的函数。
函数通常都有其自身的使用文档。使用DESCRIBE FUNCTION命令可以展示对应函数简短的介绍。函数也可能包含更多的详细文档,可以通过增加EXTENDED关键字进行查看。

Hive中有3种UDF:

- UDF:操作单个数据行,产生单个数据行;
- UDAF:操作多个数据行,产生一个数据行;
- UDTF:操作一个数据行,产生多个数据行一个表作为输出。

本篇文章主要围绕标准函数进行介绍,所谓的标准函数,UDF这个术语在狭义上的概念表示以一行数据中的一列或者多列作为参数,然后返回结果是一个值的函数。大多数函数都是属于这类的。
常见的标准函数,比如数学函数(round()floor()),还有字符串操作函数,比如ucase(),reverse()concat()等等。

标准UDF的实现步骤如下:

- 自定义一个java类
- 继承UDF类
- 重写evaluate方法
- 打包类所在项目成一个all-in-one的jar包并上传到hive所在机器
- 在hive中执行持久化jar包操作,将jar加载到/usr/lib/inceptor/lib/中。
- 在hive中创建模板函数,使得后边可以使用该函数名称调用实际的udf函数
- hive sql中像调用系统函数一样使用udf函数

下面我们开始编写自己的UDF。假设我们有一张表,表中的一个字段存储的是每个用户的生日。通过这个信息,我们期望能够计算出每个人所属的星座。

详细说明


1. 准备数据

下面是一个样本数据集,我们将其放到用户根目录下一个名为littlebigdata.txt的文件中

edward capriolo,edward@media6degrees.com,2-12-1981,209.191.139.200,M,10
bob,bob@test.net,10-10-2004,10.10.10.1,M,50
sara connor,sara@sky.net,4-5-1974,64.64.5.1,F,2
# shell
> hadoop fs -mkdir /tmp/littlebigdata
> hadoop fs -put ./littlebigdata.txt /tmp/littlebigdata

将样本数据载入到名为littlebigdata的表中

-- sql
CREATE EXTERNAL TABLE littlebigdata(
name string,
email string,
bday string,
ip string,
gender string,
anum int)
ROW  FORMAT DELIMITED FIELDS TERMINATED BY ',' 
LOCATION '/tmp/littlebigdata';

函数的输入是一个日期bday,而函数输出表示该用户星座的字符串。

2. 代码实现

编写一个UDF,需要继承UDF类并实现evaluate()函数。在查询执行过程中,查询中对应的每个应用到这个函数的地方都会对这个类进行实例化。对于每行输入都会调用到evaluate()函数。而evaluate()处理后的值会返回给Hive。同时用户是可以重载evaluate方法的。Hive会像Java的方法重载一样,自动选择匹配的方法。

代码中的@Description(…)表示的是Java的注解,是可选的。注解中注明了关于这个函数的文档说明,用户需要通过这个注解来阐述自定义的UDF的使用方法和例子。这样当用户通过DESCRIBE FUNCTION ... 命令查看该函数时,注解中的_FUNC_字符串将会被替换为用户为这个函数定义的“临时”函数名称。

org.apache.hadoop.hive.ql.exec.Description和org.apache.hadoop.hive.ql.exec.UDF这两个类,可以到inceptor容器内/usr/lib/inceptor/lib/路径下获取

//import org.junit.jupiter.api.Test;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDF;
/**
 * Demo class
 *
 * @author kevin
 * @date 2020/04/17
 */
@Description(name="zodiac",
        value = "_FUNC_(date) - from the input date string "+"or separate month and day arguments, returns the sign of the Zodic.",
        extended = "Example:\n"
                + "> SELECT _FUNC_(date_string) FROM src;\n"
                + "> SELECT _FUNC_(month,day) FROM src;\n")
public class zodiac extends UDF {
    private  SimpleDateFormat df ;
    public zodiac() {
        df = new SimpleDateFormat("MM-dd-yyyy");
    }
    public  String evaluate(String bday){
        Date date =null;
        try{
            date = df.parse(bday);
        }
        catch(Exception ex){
            System.out.println("异常");
            ex.printStackTrace();
            return null;
        }
        //bday.getMonth()的返回值范围是0-11,因此获取月份的写法应该是bday.getMonth()+1;而getDay()返回的是一周中的第几天,应该用getDate()返回月中的天数
        return evaluate(date.getMonth()+1,date.getDate());
    }
    public  String evaluate(Integer month,Integer day){
        if(month == 1){
            if(day<20){
                return "摩羯座";
            }else{
                return "水瓶座";
            }
        }
        if(month == 2){
            if(day<19){
                return "水瓶座";
            }else{
                return "双鱼座";
            }
        }
        if(month == 3){
            if(day<20){
                return "双鱼座";
            }else{
                return "白羊座";
            }
        }
        if(month ==4){
            if(day<20){
                return "白羊座";
            }else{
                return "金牛座";
            }
        }
        if(month ==5){
            if(day<20){
                return "金牛座";
            }else{
                return "双子座";
            }
        }
        if(month ==6){
            if(day<21){
                return "双子座";
            }else{
                return "巨蟹座";
            }
        }
        if(month ==7){
            if(day<22){
                return "巨蟹座";
            }else{
                return "狮子座";
            }
        }
        if(month ==8){
            if(day<23){
                return "狮子座";
            }else{
                return "处女座";
            }
        }
        if(month ==9){
            if(day<22){
                return "处女座";
            }else{
                return "天秤座";
            }
        }
        if(month ==10){
            if(day<24){
                return "天秤座";
            }else{
                return "天蝎座";
            }
        }
        if(month ==11){
            if(day<22){
                return "天蝎座";
            }else{
                return "射手座";
            }
        }
        if(month ==12){
            if(day<22){
                return "射手座";
            }else{
                return "摩羯座";
            }
        }
        return null;
    }

//    @Test
//    public void test() {
//        zodiac aa = new zodiac();
//        String str = aa.evaluate("3-22-1991");
//        String str = aa.evaluate(3,22);
//        System.out.println(str);
//    }

//    public static void main(String[] args) {
//        zodiac aa = new zodiac();
//        String str = aa.evaluate("3-22-1991");
//        String str = aa.evaluate(3,22);
//        System.out.println(str);
//    }
}

3. 打包成jar包

此处忽略,可自行百度不同命令或IDE工具的打包方式。

注意:打包时,jdk版本要和inceptor内的jdk版本保持一致,否则会报java.lang.UnsupportedClassVersionError: InceptorUDAF : Unsupported major.minor version 52.0的问题

4. UDF持久化

**4.X版本:**
1) udf.jar放到/usr/lib/hive/lib中(每个executor节点);
2) add jar  /usr/lib/hive/lib/udf.jar;
3) create permanent function func_name as 'package.classname';

**5.X版本以上**
1) 把udf.jar放到/usr/lib/inceptor/lib目录下,push到镜像仓库;
2) 重启inceptor来获取最新镜像;
3) create permanent function func_name as 'package.classname';

注意:
1) udf无论是临时还是永久,如果要删除并重新创建使用相同类或者jar的话,都要重启inceptor server;
2) 官方不推荐使用using jar的方式是因为此方法存在异常隐患,会报找不到jar包的错误。

这里以TDH6.0.2版本环境为例:
首先将udf.jar放到/usr/lib/inceptor/lib目录下,push到镜像仓库中,重启inceptor来拉取最新镜像;

--创建永久函数
> CREATE PERMANENT FUNCTION zodiac AS 'zodiac';
--测试UDF函数,并查看该function的具体使用描述
> SELECT t.*,zodiac(bday) AS constellation FROM littlebigdata t;
> DESCRIBE FUNCTION EXTENDED zodiac;

这篇文章对您有帮助吗?

平均评分 5 / 5. 次数: 2

尚无评价,您可以第一个评哦!

非常抱歉,这篇文章对您没有帮助.

烦请您告诉我们您的建议与意见,以便我们改进,谢谢您。