全部產品
Search
文件中心

MaxCompute:UDT樣本

更新時間:Jun 19, 2024

本文為您提供常用的UDT樣本。例如Java數組、JSON、複雜類型、彙總操作、資料表值函式、函數重載和引用嵌入式代碼。

說明 請您在指令碼模式下運行如下範例程式碼,指令碼模式詳情請參見SQL指令碼模式

Java數組

set odps.sql.type.system.odps2=true;
set odps.sql.udt.display.tostring=true;
SELECT
    new Integer[10],    --建立一個包含10個元素的數組。
    new Integer[] {c1, c2, c3},  --通過初始化列表建立一個長度為3的數組。
    new Integer[][] { new Integer[] {c1, c2}, new Integer[] {c3, c4} },  --建立多維陣列。
    new Integer[] {c1, c2, c3} [2], --通過下標操作訪問數組元素。
    java.util.Arrays.asList(c1, c2, c3)   --建立一個List<Integer>,也能當做array<int>來用,是另一種建立內建ARRAY資料的方法。
FROM VALUES (1,2,3,4) AS t(c1, c2, c3, c4);

JSON

UDT運行時內建一個JSON的依賴(2.2.4),可以直接使用JSON。
說明 除JSON外,MaxCompute運行時內建的依賴還包括commons-logging(1.1.1)commons-lang(2.5)commons-io(2.4)protobuf-java(2.4.1)
set odps.sql.type.system.odps2=true;
set odps.sql.session.java.imports=java.util.*,java,com.google.gson.*; --同時匯入多個Package時用逗號隔開。
@a := select new Gson() gson;   --構建Gson對象。
select 
gson.toJson(new ArrayList<Integer>(Arrays.asList(1, 2, 3))), --將任意對象轉成JSON字串。
cast(gson.fromJson('["a","b","c"]', List.class) as List<String>) --還原序列化JSON字串,注意Gson的介面,直接還原序列化後是List<Object>類型,這裡強制轉換成List<String>,方便後續使用。
from @a;

相比於內建函數字串函數,該方法不僅使用方便,還會在提取JSON字串內容時,將JSON字串還原序列化為格式化資料,提升工作效率。

複雜類型

內建類型ARRAY與java.util.List、MAP和java.util.Map存在映射關係。
  • Java中實現java.util.Listjava.util.Map介面類的對象,都可參與MaxCompute SQL的複雜類型操作。
  • MaxCompute中ARRAY或MAP的資料,能夠直接調用List或者MAP的介面。
set odps.sql.type.system.odps2=true;
set odps.sql.session.java.imports=java.util.*;
select
    size(new ArrayList<Integer>()),        --對ArrayList資料調用內建函數Size。
    array(1,2,3).size(),                   --對內建類型ARRAY調用List的Size方法。
    sort_array(new ArrayList<Integer>()),  --對ArrayList的資料進行排序。
    al[1],                                 --雖然Java的List不支援下標操作,但ARRAY支援。
    Objects.toString(a),        --之前不支援將ARRAY類型Cast成STRING,現在有繞過方法了。
    array(1,2,3).subList(1, 2)             --計算subList。
from (select new ArrayList<Integer>(array(1,2,3)) as al, array(1,2,3) as a) t;

彙總操作

UDT實現彙總的原理是,先用內建函數COLLECT_SETCOLLECT_LIST將資料轉變成List,之後對該List應用UDT的標量方法計算資料的彙總值。

樣本如下,計算BigInteger的中位元(由於資料是java.math.BigInteger類型的,所以不能直接用內建函數MEDIAN)。
set odps.sql.session.java.imports=java.math.*;
@test_data := select * from values (1),(2),(3),(5) as t(value);
@a := select collect_list(new BigInteger(value)) values from @test_data;  --先把資料彙總成List。
@b := select sort_array(values) as values, values.size() cnt from @a;  --計算中位元的邏輯,先將資料排序。
@c := select if(cnt % 2 == 1, new BigDecimal(values[cnt div 2]), new BigDecimal(values[cnt div 2 - 1].add(values[cnt div 2])).divide(new BigDecimal(2))) med from @b;
--最終結果。
select med.toString() from @c;

collect_list會先將所有資料都收集到一起,無法實現Partial Aggregate,處理效率比內建彙總函式或者UDAF低,請盡量使用內建彙總函式。同時,把一個Group的所有資料都收集到一起,會增加資料扭曲的風險。

如果UDAF的邏輯是要將所有資料收集到一起(例如類似內建彙總函式WM_CONCAT的功能),使用上述方法,處理效率比UDAF高。

資料表值函式

資料表值函式允許輸入多行多列資料,輸出多行多列資料。可以按照如下操作實現:
  • 輸入多行多列資料,詳情請參見彙總操作
  • 輸出多行多列資料,可以用UDT方法輸出一個Collection類型的資料(List或者MAP),然後調用Explode函數,將Collections展開成多行。
  • UDT可以包含多個資料域,通過調用不同的Getter方法擷取各個域的內容即可展開成多列。
展開一個JSON字串的內容,樣本如下。
@a := select '[{"a":"1","b":"2"},{"a":"1","b":"2"}]' str; --樣本資料。
@b := select new com.google.gson.Gson().fromJson(str, java.util.List.class) l from @a; --還原序列化JSON。
@c := select cast(e as java.util.Map<Object,Object>) m from @b lateral view explode(l) t as e;  --用explode展開成多行。
@d := select m.get('a') as a, m.get('b') as b from @c; --展開成多列。
select a.toString() a, b.toString() b from @d; --最終結果輸出(注意變數d的輸出中a、b兩列是Object類型)。

函數重載

MaxCompute UDF使用evaluate方法重載函數。該方式不支援泛型,當您需要定義一個支援任何資料類型的函數時,必須為每種類型都寫一個evaluate函數。該方法無法實現個別輸入類型(例如ARRAY)的重載函數。在沒有提供Resolve註解的情況下,Python UDF或UDTF會根據參數個數決定輸入參數,同時支援變長參數,但會導致編譯器無法靜態找到某些錯誤。

通過UDT實現函數重載,可以解決上述問題。UDT支援泛型、類繼承和變長參數,為您提供靈活的函數定義方式,樣本如下。
public class UDTClass {
    // 這個函數支援一個數實值型別(可以是TINYINT、SMALLINT、INT、BIGINT、FLOAT、DOUBLE以及任何以Number為基類的UDT),返回DOUBLE。
    public static Double doubleValue(Number input) {
        return input.doubleValue();
    }
    // 這個方法支援一個數實值型別參數和一個任意類型的參數,傳回值類型與第二個參數的類型相同。
    public static <T extends Number, R> R nullOrValue(T a, R b) {
        return a.doubleValue() > 0 ? b : null;
    }
    // 這個方法支援一個任意元素類型的ARRAY或List,返回BIGINT。
    public static Long length(java.util.List<? extends Object> input) {
        return input.size();
    }
    // 注意在不做強制轉換的情況下參數只支援UDT的java.util.Map<Object, Object>對象。如果需要傳入任何MAP對象,例如map<bigint,bigint>可以考慮:
    // 1. 定義函數時使用java.util.Map<? extends Object, ? extends Object>。
    // 2. 調用時做強制轉換,例如UDTClass.mapSize(cast(mapObj as java.util.Map<Object, Object>))。
    public static Long mapSize(java.util.Map<Object, Object> input) {
        return input.size();
    }
}

特定情境下,UDF可以通過com.aliyun.odps.udf.ExecutionContext(在setup方法中傳入)擷取上下文;UDT可以通過com.aliyun.odps.udt.UDTExecutionContext.get()方法擷取ExecutionContext對象。

UDT引用嵌入式代碼

代碼嵌入式UDF允許您將SQL指令碼和第三方代碼放入同一個源碼檔案,減少使用UDT的操作步驟,方便日常開發。詳情請參見UDF(嵌入式)