前言:之前在实习过程中,有一个业务需求,因为开始没有理解需求,所以走了很多弯路,最后修改代码逻辑的时候发现还有上游字段没有解析,需要些一个udtf来完成需求。之前虽然了解过udf和udtf等,但是没有实际写过,
最后差点任务延期,所以在使用hive过程中,udf和udtf也应该作为数据开发人员的基本功,在实际开发过程中也是经常需要使用的。

1、UDF:只对单行数值产生作用;继承UDF类,核心方法evaluate();

注意:evaluate()方法并不是唯一的:
这里我以一个实际方法为例来讲解:
public class DateBetween extends UDF {
	public boolean evaluate(String diffDates[],String inDate,int num) throws ParseException {
		if(diffDates == null) return false;
		for(String date:diffDates){
			int diff = diffDays(date,inDate);
			if(diff>0 && diff<=num) return true;
		}
		return false;
	}
	public boolean evaluate(String diffDate,String inDate,int num) throws ParseException {
		if(diffDate == null ||"".equals(diffDate))return false;
		String diffDates[] = diffDate.split(",");
		for(String date:diffDates){
			int diff = diffDays(date,inDate);
			if(diff>0 && diff<=num) return true;
		}
		return false;
	}
	public int diffDays(String dateStr1,String dateStr2) throws ParseException {
		if(dateStr1 == null || dateStr2 ==  null)return -1;
		SimpleDateFormat df = null;
		df = new SimpleDateFormat("yyyyMMdd");
		Date date1= df.parse(dateStr1);
		Date date2 = df.parse(dateStr2);
		long time = date1.getTime() - date2.getTime();
		if(time<=0){
			return -1;
		} else{
			return (int)(time/(24 * 60 * 60 * 1000));
		}
	}

	public static void main(String srgas[]) throws ParseException {
		DateBetween between = new DateBetween();
		String dateStr = "20211026,20211101";
		String dateStr2 = "20211030";
		String diffDates[] = dateStr.split(",");
		for(String date:diffDates){
			int diff = between.diffDays(date,dateStr2);
			System.out.println("diff:"+diff);
			if(diff>0 && diff<=7)
				System.out.println(1);
		}
	}
}
evaluate方法中定义参数及返回值,这也是udf_func(x)的返回值,可以看到,evaluate()是可以进行方法重载的。

2、udtf:输入一行输出多行;继承GenericUDTF类,重写initialize(返回输出行信息:列个数,类型), process, close三方法;

public class CouponRestrictionPoiLimitlist extends GenericUDTF{

    private static final String POI_SKU = "poi_sku_ID";

    private static Logger logger= LoggerFactory.getLogger(CouponRestrictionPoiLimitlist.class);
    static final Log LOG = LogFactory.getLog(CouponRestrictionPoiLimitlist.class.getName());

    private String[] obj = new String[1];

//初始化方法,主要作用是定义输出的格式、字段类型;
    @Override
    public StructObjectInspector initialize(ObjectInspector[] argOIs) throws UDFArgumentException {

        ArrayList<String> fieldNames = new ArrayList<String>();
        ArrayList<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>();

//如果有多列需要输入和输出,那么同样也应该在这里定义多个对象,具体方法便是fieldNames和fieldOIs应该要对应添加想要输出的内容;
        fieldNames.add(POI_SKU);
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

//一般来说,这里选用默认的输出结构即可;
        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
    }

    @Override
    public void process(Object[] objects) throws HiveException {

        try{
            //字段值;
            String jsonStr = objects[0].toString();

            logger.warn("####"+jsonStr);

            //提取JSON字段;
            Gson gson = new Gson();

            JSONArray jsonArray = gson.fromJson(jsonStr, new TypeToken<JSONArray>() {}.getType());

            for (int i = 0; i < jsonArray.size(); i++) {

                JSONObject jsonObject = jsonArray.getJSONObject(i);
                //获取poiId字段;
                Integer poiId = jsonObject.getInteger("poiId");
                //获取limitList字段;
                List<Integer> limitList = jsonObject.getObject("limitList", List.class);
                //拼接并输出poi_sku字段;
                for (int j = 0; j < limitList.size(); j++) {
                    Integer skuId = (int) Double.parseDouble(String.valueOf(limitList.get(j)));
                    String result=poiId + "_" + skuId;
                    obj[0] = result;
                    logger.warn("####result"+result);
//这里要注意,forward()中输出的是Object类型数据,但实际上这里不能直接输出单个Object对象,而应该是String[],Object数组等类型数据;
                    forward(obj);
                }
            }
        }catch(Exception e){
            e.printStackTrace();
            logger.error(e.getMessage(), e);
        }
    }

    @Override
    public void close() throws HiveException {

    }



    public static void main(String[] args) {

        String jsonStr = "[{\"poiId\":46,\"limitList\":[54183,54318,15501,54849]}]";

        //提取JSON字段;
        Gson gson = new Gson();

        JSONArray jsonArray = gson.fromJson(jsonStr, new TypeToken<JSONArray>() {}.getType());

        for (int i = 0; i < jsonArray.size(); i++) {

            JSONObject jsonObject = jsonArray.getJSONObject(i);
            //获取poiId字段;
            Integer poiId = jsonObject.getInteger("poiId");
            //获取limitList字段;
            List<Integer> limitList = jsonObject.getObject("limitList", List.class);
            //拼接并输出poi_sku字段;
            for (int j = 0; j < limitList.size(); j++) {
                Integer skuId = (int) Double.parseDouble(String.valueOf(limitList.get(j)));
                System.out.println((poiId + "_" + skuId));
            }
        }
    }
}

//输出结果为:
46_54183
46_54318
46_15501
46_54849

Process finished with exit code 0

之前有人问我这种udtf函数应该怎么使用?

我这里补充一下,如果公司有平台的话,一般会提供一个命名的参数,即赋予对应jar包一个函数名,在linux上进行部署,具体可参考文档:
udf部署 对于udf函数而言,之前自己写了什么字段,udf_func(x)中的x参数就写啥,如果是udtf函数,那么需要注意一下了,我们需要输出多行数据,实际上数据会自动一行一行的与其他数据进行拼接,所以我们只需要控制输出的字段(即列)即可:

!!注意,udf函数和udtf函数的一个很大的不用应用点在于,udtf函数不能直接use ... as ...;需要用到LATERAL VIEW,以上面的例子来说明:
如果我这里以couponPoiSku来表示udf后的函数名,name最终的写法可以是这样:

select t.a,t.b,tt.c from tableXXX t LATERAL VIEW couponPoiSku(`data`) tt as poi_sku;

//此时这里的`data`,a,b字段为表tableXXX中的字段,c为udtf输出的数据;

如果我们还想把46_54849这种数据拆成46,54849两个字段类型,那么我们可以用split函数来实现:
select
		 a,
		 b,
         cast(split(poi_sku,'_')[0] as bigint) as poi_id,
         cast(split(poi_sku,'_')[1] as bigint) as base_sku_id
         from (
   select t.a,t.b,tt.c from tableXXX t LATERAL VIEW couponPoiSku(`data`) tt as poi_sku
         );

3、udaf函数;这里我没有用过,以后有遇到会再行补充;主要运用得多的是udf和udtf函数;