前言
-
Hive:2.3.0
-
由於實際生產環境中,Hive自帶的內建函數無法覆蓋所有的應用場景,所以時常需要進行自定義函數User-Defined Function(UDF),以滿足實際生產需求。
-
本文主要演示如何實現自定義表生成函數User-Defined Table-Generating Function(UDTF),此類函數的特點是一進多出
-
創建Hive函數時,如果指定爲臨時的(temporary)則可以在所有數據庫下使用,但只能在當前會話中使用,退出後自動刪除;如果指定爲持久的(permanent)則只能在指定數據庫(默認當前數據庫)下使用,但可以在其他會話中使用,退出後依舊保留
-
溫馨提示:Hive自定義組件打包成jar包時,不要同時打包依賴,避免各種版本衝突,只將額外的依賴添加到classpath中即可
自定義UDTF函數的實現步驟
1)繼承org.apache.hadoop.hive.ql.udf.generic.GenericUDTF
類
2)實現initialize
,process
,close
方法
3)打包成jar文件
4)在Hive CLI中將jar包添加到classpath
add jar <path_to_jar>
5)創建對應函數
create function <db_name>.<function_name> AS '<full_class_name>'
自定義UDTF實現Demo
自定義實現了UDTF函數split_explode,此函數的功能是,將傳入的字符串按照指定的正則表達式拆分成字符串數組,然後形成多個列,類似於split函數和explode函數的功能組合,源碼如下:
package com.tomandersen.hive.udtfs;
import org.apache.hadoop.hive.ql.exec.Description;
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.ql.udf.generic.GenericUDTFExplode;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTFJSONTuple;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTFPosExplode;
import org.apache.hadoop.hive.serde.serdeConstants;
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.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
import org.apache.hadoop.io.Text;
import java.util.ArrayList;
import java.util.List;
/**
* <h3>Hive自定義表生成函數UDTF的實現Demo</h3>
* 功能: 將字符串按照指定分隔符分割成獨立的單詞,並返回多行.
* <p>
* UDTF輸入: 待分割字符串,分隔符正則表達式
* UDTF輸出: 分割後產生的單詞列
* <p>
* 參考: {@link GenericUDTFJSONTuple},{@link GenericUDTFExplode},{@link GenericUDTFPosExplode}
*
* @author TomAndersen
* @version 1.0
* @date 2020/5/21
*/
// 用於生成Function document.可以使用desc function <function_name>命令查看函數使用文檔.
@Description(
name = "split_explode",
value = "_FUNC_(str,regex) - Splits str around occurrences that match regex and " +
"separates the elements into multiple rows."
)
public class CustomUDTFSplitAndExplode extends GenericUDTF {
// 保存所有輸入字段對應的 ObjectInspector
private transient ObjectInspector[] inputFieldIOs;
// 保存輸出列個數
private int numCols;
// 保存輸出列
private transient Text[] retCols;
// 保存空輸出列,用於在特殊情況輸出
private transient Object[] nullCols;
// 保存輸入參數中的正則表達式
private String regex;
// 保存正則表達式regex是否已經獲取
private boolean regexParsed = false;
/**
* 此方法一般用於檢查輸入參數是否合法,每個實例只會調用一次.
* 一般用於用於檢查UDTF函數的輸入參數,以及生成一個用於查看新生成字段數據的StructObjectInspector.
*
* @param argOIs {@link StructObjectInspector},傳入的是所有輸入參數字段的查看器
* @return {@link StructObjectInspector},返回的是新生成字段的查看器
* @date 2020/5/22
*/
@Override
public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
// 1.輸入參數檢查
List<? extends StructField> inputFields = argOIs.getAllStructFieldRefs();// 獲取所有的輸入參數字段
int argsLength = inputFields.size();
inputFieldIOs = new ObjectInspector[argsLength];// 獲取所有輸入參數字段對應的檢查器(Inspector)
for (int i = 0; i < argsLength; i++) {
inputFieldIOs[i] = inputFields.get(i).getFieldObjectInspector();
}
if (argsLength != 2) {// 檢查參數個數
throw new UDFArgumentException("split_explode(str,regex) takes only two arguments: " +
"the string and regular expression");
}
for (int i = 0; i < argsLength; ++i) {// 檢查參數類型
if (inputFieldIOs[i].getCategory() != ObjectInspector.Category.PRIMITIVE ||
!inputFieldIOs[i].getTypeName().equals(serdeConstants.STRING_TYPE_NAME)) {
throw new UDFArgumentException("split_explode(str,regex)'s arguments have to be string type");
}
}
// 2.成員變量賦值
numCols = 1;
retCols = new Text[numCols];
nullCols = new Object[numCols];
for (int i = 0; i < numCols; i++) {
retCols[i] = new Text();
nullCols[i] = null;
}
// 3.定義輸出字段的列名(字段名)
// 此處設置的各個列名很可能會在用戶執行Hive SQL查詢時,會被其指定的列別名所覆蓋,如果查詢時未指定別名,則顯示此列名.
// 此自定義表生成函數UDTF只輸出單列,所以只添加了單個列的列名
ArrayList<String> fieldNames = new ArrayList<>();
fieldNames.add("split_explode_col");
// 4.定義輸出字段對應字段類型的ObjectInspector(對象檢查器),一般用於查看/轉換/操控該字段的內存對象
// 只是查看器,並非是真正的字段對象
ArrayList<ObjectInspector> fieldOIs = new ArrayList<>();
// 由於輸出字段的數據類型都是String類型
// 因此使用檢查器PrimitiveObjectInspectorFactory.javaStringObjectInspector
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
// 5.生成並返回輸出對象的對象檢查器
return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
}
// 此方法爲核心處理方法,聲明主要的處理過程,如果是對錶使用函數,則會對錶中每一行數據通過同一個實例調用一次此方法.
// 輸入參數爲待處理的字符串以及分隔符正則表達式,每次處理完後直接使用forward方法將數據傳給收集器.
@Override
public void process(Object[] args) throws HiveException {
// 1.通過 ObjectInspector 獲取待處理字符串
String str = ((StringObjectInspector) inputFieldIOs[0]).getPrimitiveJavaObject(args[0]);
if (str == null || str.length() == 0) {// 如果對象爲null則直接輸出空行
forward(nullCols);
return;
}
// 2.獲取分割符正則表達式
if (!regexParsed) {
regex = ((StringObjectInspector) inputFieldIOs[1]).getPrimitiveJavaObject(args[1]);
regexParsed = true;
}
// 3.按照正則表達式分割字符串
String[] words = str.split(regex);
// 4.輸出數據
// 將輸出行通過forward方法傳給收集器,每調用一次forward則代表在新生成字段中生成一行數據
for (String word : words) {
// 將輸出數據放入輸出數組的指定位置(每次寫入時爲覆蓋寫入),然後輸出.本UDTF固定輸出單列.
retCols[0].set(word);
forward(retCols);
}
}
// 釋放相關資源,一般不需要
@Override
public void close() throws HiveException {
}
// 返回此UDTF對應的函數名
@Override
public String toString() {
return "split_explode";
}
}