Hive中自定義函式

Begonia_sea發表於2020-10-13

Hive中自定義函式


一、概述

--1. 在hive中有三種自定義函式:
1. UDF  :一進一出  --一行變一行
2. UDTF : 一進多出  -- 一行變多行
3. UDAF :多進一出  -- 多行變一行

-- 2. 實現步驟:
   a、進入函式的是什麼引數
   b、希望得到什麼結果
   c、考慮通用性

二、UDTF函式

2.1 UDTF解析

-- 1. 說明
A custom UDTF can be created by extending the GenericUDTF abstract class and then implementing the initialize, process, and possibly close methods. The initialize method is called by Hive to notify the UDTF the argument types to expect. The UDTF must then return an object inspector corresponding to the row objects that the UDTF will generate. Once initialize() has been called, Hive will give rows to the UDTF using the process() method. While in process(), the UDTF can produce and forward rows to other operators by calling forward(). Lastly, Hive will call the close() method when all the rows have passed to the UDTF.

    -- 解析如上內容:
    1. 自定義UDTF函式,繼承於抽象類GenericUDTF;
    2. 實現initialize, process,close 的三個方法;
    3. 三個方法的作用說明:
       1. 'initialize()':
          a、規定形參的引數型別:Hive呼叫initialize()方法,來告訴UDTF函式接收引數的型別,然後在這個方法中就可以對引數的
          型別進行校驗,引數型別包括個數、資料型別等等
          b、規定函式返回結果資料型別:返回initialize()方法必須返回一個與UDTF將生成的行物件對應的物件檢查器   
       2. 'process()':
          a、呼叫時機:initialize()被呼叫以後,即對輸入的資料進行了檢查滿足條件以後,執行這個方法
          b、作用:一行資料呼叫一次process方法,process方法的內部,會遍歷這一行資料,如果是陣列,那就遍歷陣列,遍歷以後的
          一個元素,呼叫一次forward()方法,對資料進行輸出。
       3. 'close()':
          a、呼叫時機:當hive中的所有行都通過了UDTF函式以後,則Hive會呼叫這個close方法
          b、作用:關閉資源。      

2.2 程式碼實現

package com.flying.hive;

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructField;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.json.JSONArray;

import java.util.ArrayList;
import java.util.List;

public class ExplodeJSONArray extends GenericUDTF {
    /*
    StructObjectInspector:結構體,輸出資料型別和返回資料型別均是結構體
    UDTF:返回的資料可以是多列,多列可以封裝到struct型別中
    [agr1:type1,agr2:type2,agr3:type3,...]
     */
    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {

        // 1. 獲取輸入的引數,並對輸入的引數進行檢測
        /*
         1.1 獲取形參,獲取的資料為一個陣列。
             List(agr1:type1,agr2:type2,agr3:type3,...)
          */
        List<? extends StructField> fieldRefs = argOIs.getAllStructFieldRefs();

        /*
         1.2 檢驗一:檢驗資料的個數是否為1個,在本案例中,傳進來一個json陣列
          */
        if (fieldRefs.size() != 1) {
            throw new UDFArgumentException("傳遞引數的個數超過1個");
        }

        /*
        1.3 校驗2 :判斷形參的資料型別是否為string型別
         */
        // 返回一個引數,因為我們規定只能是一個引數,返回引數的檢查器
        ObjectInspector fieldObjectInspector = fieldRefs.get(0).getFieldObjectInspector();

        // 通過引數的檢查器返回引數的資料型別判定需要是字串型別
        if (!"string".equals(fieldObjectInspector.getTypeName())) {
            throw new UDFArgumentException("傳入的引數型別錯誤,需要是string型別");
        }

        // 2. 返回函式結果的檢查器
        // 建立兩個陣列,一個用來包裝列名,另外一個用來包裝列名的資料型別
        // 陣列1:包裝列名
        List<String> filenames = new ArrayList<String>();
        // 陣列2:包裝列名的資料型別
        ArrayList<ObjectInspector> fileOIS = new ArrayList<ObjectInspector>();

        // 新增元素
        filenames.add("actions");
        fileOIS.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

        return ObjectInspectorFactory.getStandardStructObjectInspector(filenames, fileOIS);
    }

    /**
     * 對資料進行處理,執行函式邏輯的方法,每一行資料會呼叫一次process方法
     * 1.為什麼是object型別?
     * 表示任意引數
     * 2.為什麼是一個陣列?
     * 表示可以傳多個
     * @param args 每一行資料會呼叫一次process方法
     * @throws HiveException
     */
    @Override
    public void process(Object[] args) throws HiveException {
        /*
         1.在initialize方法中,已經對輸入的資料進行校驗,此時的形參中只能是一個
         引數,引數為一個json陣列。
       [{"action_id":"favor_add","item":"7","item_type":"sku_id","ts":1592668888084}]
           */
        String json = args[0].toString();
        /*
        2. 對上面的json進行解析
        jsonArray : List({"action_id":"favor_add","item":"7","item_type":"sku_id","ts":1592665678084},....)
         */
        JSONArray jsonArray = new JSONArray(json);

        // 3. 遍歷上面的json陣列,行中的每一個資料都需要呼叫一次forward方法,將結果進行寫出
        for(int i = 0 ; i < jsonArray.length() ; i++){
            // 建立一個陣列,陣列的長度為1
            String[] result = new String[1];
            // 獲取jsonArray指定位置的json資料,並存放到陣列中
            result[0] = jsonArray.getString(i);
            forward(result);

        }

    }

    @Override
    public void close() throws HiveException {

    }
}

三、UDF函式

3.1 程式碼實現

package com.flying.hive.udf;

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ConstantObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;


public class JsonArrayToStructArray extends GenericUDF {

    /*
    本案例中實現的目的:
    原始資料:
   [{"action_id":"favor_add","item":"5","item_type":"sku_id","ts":1592668884186},{...}]
     轉換後資料:
     array<struct("action_id":"favor_add","item":"5","item_type":"sku_id","ts":1592668884186),struct(...)>
     */

    /**
     * 用來校驗輸入的引數型別和個數,同時返回函式返回結果的檢查器
     *
     * @param arguments 輸入的引數
     * @return
     * @throws UDFArgumentException
     */
    @Override
    public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {

    /*
    1. 為了實現程式碼的通用性,由傳入的形參的資料列型別和列名來確定返回值的檢查器。
    2. 則在實際的呼叫的過程中,使用:如下案例所示
       a、"id","name","age",表示從json物件獲取的欄位
       b、“id:string”,"name:string","age:int":表示每個欄位的資料型別
       如上兩個引數完全由使用者自定義,作為引數傳入到udf的方法中。
    JsonArrayToStructArray(jsonarray,"id","name","age",“id:string”,"name:string","age:int")

     3. 實現步驟:
        a、獲取形參中的第一個引數,返回一個字串型別的json物件
        array<Struct<k1:v1,k2:v2,k3:v3,....>
        b、建立兩個集合,一個集合用來接收k1~kn列名,另外一個集合用來接收v1~vn的資料型別
     */
        // 1. 對輸入的引數進行校驗
        // 判斷引數的個數
        if (arguments.length < 3) {
            throw new UDFArgumentException("JsonArrayToStructArray至少需要3個引數");
        }
        // 判斷引數的資料型別
        for (ObjectInspector argument : arguments) {
            if (!"string".equals(argument.getTypeName())) {
                throw new UDFArgumentException("JsonArrayToStructArray輸入的引數型別不對,只能是string型別");
            }
        }

        // 2. 對返回結果的資料型別和名字進行封裝
        // array<Struct<k1:v1,k2:v2,k3:v3,....>,...>
        List<String> FieldNames = new ArrayList<>();
        List<ObjectInspector> fieldOIS = new ArrayList<>();

        // 2.1 獲取傳遞引數的“id:string”,"name:string","age:string"
        //     然後將id、name,age加入到FieldNames中
        //     將string、string、int加入到fieldOIS中
        // JsonArrayToStructArray(jsonarray,"id","name","age",“id:string”,"name:string","age:int")
        for (int i = (arguments.length + 1) / 2; i < arguments.length; i++) {
            if (!(arguments[i] instanceof ConstantObjectInspector)) {
                throw new UDFArgumentException("JsonArrayToStructArray輸入的引數型別不對,只能是string常量");
            }
            // 此時獲取:field=“id:string”
            String field = ((ConstantObjectInspector) arguments[i]).getWritableConstantValue().toString();
            // 對上訴資料進行切分
            String[] split = field.split(":");
            // 新增資料
            FieldNames.add(split[0]);
            switch (split[1]) {
                case "int":
                    fieldOIS.add(PrimitiveObjectInspectorFactory.javaIntObjectInspector);
                    break;
                case "string":
                    fieldOIS.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
                    break;
                case "bigint":
                    fieldOIS.add(PrimitiveObjectInspectorFactory.javaLongObjectInspector);
                    break;
                default:
                    throw new UDFArgumentException("json_array_to_struct_array 不支援" + split[1] + "型別");
            }
        }
        return ObjectInspectorFactory
               .getStandardListObjectInspector(ObjectInspectorFactory.getStandardStructObjectInspector(FieldNames, fieldOIS));
    }
    /**
     * 處理的邏輯
     * array型別:傳入一個陣列或集合
     * map型別:Map
     * struct:陣列或集合
     *
     * @param arguments
     * @return
     * @throws HiveException
     */
    @Override
    public Object evaluate(DeferredObject[] arguments) throws HiveException {
        // JsonArrayToStructArray(jsonarray,"id","name","age",“id:string”,"name:string","age:int")
        // arguments = 'jsonarray',"id","name","age",“id:string”,"name:string","age:int"

        // 獲取第一個資料jsonarray,判斷是否為空
        if (arguments[0].get() == null) {
            return null;
        };
        // 獲取第一個資料jsonarray:"[{"name":"scala"},{"name":"python"}]"
        String strArray = arguments[0].get().toString();
        // 解析json資料:jsonArray = {"name":"scala"},{"name":"python"}
        JSONArray jsonArray = new JSONArray(strArray);
        // 建立一個集合,最外層的集合
        //array<struct("action_id":"favor_add","item":"5","item_type":"sku_id","ts":1592668884186),struct(...)>
        ArrayList<List<Object>> array = new ArrayList<>();
        // 接下來就是遍歷這個json物件,每一個物件封裝成struct型別的物件
        for (int i =0 ; i < jsonArray.length() ; i ++){
            // 建立一個集合,用來接收value值
            ArrayList<Object> struct = new ArrayList<>();
            // 獲取一個json物件:{"name":"scala"}
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            // 遍歷"id","name","age"
            for (int j = 1 ; j < (arguments.length + 1 ) / 2 ;j++){
             // 輪詢的方式獲取"id","name","age"
                String key = arguments[j].get().toString();
               // 判斷當前的json物件中是否有key
                if (jsonObject.has(key)){
                    // 獲取key的value值
                    Object value = jsonObject.get(key);
                    // 新增元素
                    struct.add(value);
                }else {
                    struct.add(null);
                }
            }
            // 將獲取json的指定key【"id","name","age"】的value值放進陣列中
            array.add(struct);
        }
        return array;
    }
    /**
     *   這個方法也可以不實現。
     * @param children
     * @return
     */
    @Override
    public String getDisplayString(String[] children) {
        // json_array_struct_array:指函式的名字
        return getStandardDisplayString("json_array_struct_array",children);
    }
}


相關文章