本文為您提供常用的UDT示例。例如Java數(shù)組、JSON、復(fù)雜類型、聚合操作、表值函數(shù)、函數(shù)重載和引用嵌入式代碼。

說(shuō)明 請(qǐng)您在腳本模式下運(yùn)行如下示例代碼,腳本模式詳情請(qǐng)參見SQL腳本模式

Java數(shù)組

set odps.sql.type.system.odps2=true;
set odps.sql.udt.display.tostring=true;
SELECT
    new Integer[10],    --創(chuàng)建一個(gè)包含10個(gè)元素的數(shù)組。
    new Integer[] {c1, c2, c3},  --通過(guò)初始化列表創(chuàng)建一個(gè)長(zhǎng)度為3的數(shù)組。
    new Integer[][] { new Integer[] {c1, c2}, new Integer[] {c3, c4} },  --創(chuàng)建多維數(shù)組。
    new Integer[] {c1, c2, c3} [2], --通過(guò)下標(biāo)操作訪問(wèn)數(shù)組元素。
    java.util.Arrays.asList(c1, c2, c3)   --創(chuàng)建一個(gè)List<Integer>,也能當(dāng)做array<int>來(lái)用,是另一種創(chuàng)建內(nèi)置ARRAY數(shù)據(jù)的方法。
FROM VALUES (1,2,3,4) AS t(c1, c2, c3, c4);

JSON

UDT運(yùn)行時(shí)自帶一個(gè)JSON的依賴(2.2.4),可以直接使用JSON。
說(shuō)明 除JSON外,MaxCompute運(yùn)行時(shí)自帶的依賴還包括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.*; --同時(shí)導(dǎo)入多個(gè)Package時(shí)用逗號(hào)隔開。
@a := select new Gson() gson;   --構(gòu)建Gson對(duì)象。
select 
gson.toJson(new ArrayList<Integer>(Arrays.asList(1, 2, 3))), --將任意對(duì)象轉(zhuǎn)成JSON字符串。
cast(gson.fromJson('["a","b","c"]', List.class) as List<String>) --反序列化JSON字符串,注意Gson的接口,直接反序列化后是List<Object>類型,這里強(qiáng)制轉(zhuǎn)換成List<String>,方便后續(xù)使用。
from @a;

相比于內(nèi)建函數(shù)字符串函數(shù),該方法不僅使用方便,還會(huì)在提取JSON字符串內(nèi)容時(shí),將JSON字符串反序列化為格式化數(shù)據(jù),提升工作效率。

復(fù)雜類型

內(nèi)置類型ARRAY與java.util.List、MAP和java.util.Map存在映射關(guān)系。
  • Java中實(shí)現(xiàn)java.util.Listjava.util.Map接口類的對(duì)象,都可參與MaxCompute SQL的復(fù)雜類型操作。
  • MaxCompute中ARRAY或MAP的數(shù)據(jù),能夠直接調(diào)用List或者M(jìn)AP的接口。
set odps.sql.type.system.odps2=true;
set odps.sql.session.java.imports=java.util.*;
select
    size(new ArrayList<Integer>()),        --對(duì)ArrayList數(shù)據(jù)調(diào)用內(nèi)置函數(shù)Size。
    array(1,2,3).size(),                   --對(duì)內(nèi)置類型ARRAY調(diào)用List的Size方法。
    sort_array(new ArrayList<Integer>()),  --對(duì)ArrayList的數(shù)據(jù)進(jìn)行排序。
    al[1],                                 --雖然Java的List不支持下標(biāo)操作,但ARRAY支持。
    Objects.toString(a),        --之前不支持將ARRAY類型Cast成STRING,現(xiàn)在有繞過(guò)方法了。
    array(1,2,3).subList(1, 2)             --計(jì)算subList。
from (select new ArrayList<Integer>(array(1,2,3)) as al, array(1,2,3) as a) t;

聚合操作

UDT實(shí)現(xiàn)聚合的原理是,先用內(nèi)建函數(shù)COLLECT_SETCOLLECT_LIST將數(shù)據(jù)轉(zhuǎn)變成List,之后對(duì)該List應(yīng)用UDT的標(biāo)量方法計(jì)算數(shù)據(jù)的聚合值。

示例如下,計(jì)算BigInteger的中位數(shù)(由于數(shù)據(jù)是java.math.BigInteger類型的,所以不能直接用內(nèi)建函數(shù)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;  --先把數(shù)據(jù)聚合成List。
@b := select sort_array(values) as values, values.size() cnt from @a;  --計(jì)算中位數(shù)的邏輯,先將數(shù)據(jù)排序。
@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;
--最終結(jié)果。
select med.toString() from @c;

collect_list會(huì)先將所有數(shù)據(jù)都收集到一起,無(wú)法實(shí)現(xiàn)Partial Aggregate,處理效率比內(nèi)置聚合函數(shù)或者UDAF低,請(qǐng)盡量使用內(nèi)置聚合函數(shù)。同時(shí),把一個(gè)Group的所有數(shù)據(jù)都收集到一起,會(huì)增加數(shù)據(jù)傾斜的風(fēng)險(xiǎn)。

如果UDAF的邏輯是要將所有數(shù)據(jù)收集到一起(例如類似內(nèi)置聚合函數(shù)WM_CONCAT的功能),使用上述方法,處理效率比UDAF高。

表值函數(shù)

表值函數(shù)允許輸入多行多列數(shù)據(jù),輸出多行多列數(shù)據(jù)。可以按照如下操作實(shí)現(xiàn):
  • 輸入多行多列數(shù)據(jù),詳情請(qǐng)參見聚合操作
  • 輸出多行多列數(shù)據(jù),可以用UDT方法輸出一個(gè)Collection類型的數(shù)據(jù)(List或者M(jìn)AP),然后調(diào)用Explode函數(shù),將Collections展開成多行。
  • UDT可以包含多個(gè)數(shù)據(jù)域,通過(guò)調(diào)用不同的Getter方法獲取各個(gè)域的內(nèi)容即可展開成多列。
展開一個(gè)JSON字符串的內(nèi)容,示例如下。
@a := select '[{"a":"1","b":"2"},{"a":"1","b":"2"}]' str; --示例數(shù)據(jù)。
@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; --最終結(jié)果輸出(注意變量d的輸出中a、b兩列是Object類型)。

函數(shù)重載

MaxCompute UDF使用evaluate方法重載函數(shù)。該方式不支持泛型,當(dāng)您需要定義一個(gè)支持任何數(shù)據(jù)類型的函數(shù)時(shí),必須為每種類型都寫一個(gè)evaluate函數(shù)。該方法無(wú)法實(shí)現(xiàn)個(gè)別輸入類型(例如ARRAY)的重載函數(shù)。在沒(méi)有提供Resolve注解的情況下,Python UDF或UDTF會(huì)根據(jù)參數(shù)個(gè)數(shù)決定輸入?yún)?shù),同時(shí)支持變長(zhǎng)參數(shù),但會(huì)導(dǎo)致編譯器無(wú)法靜態(tài)找到某些錯(cuò)誤。

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

特定場(chǎng)景下,UDF可以通過(guò)com.aliyun.odps.udf.ExecutionContext(在setup方法中傳入)獲取上下文;UDT可以通過(guò)com.aliyun.odps.udt.UDTExecutionContext.get()方法獲取ExecutionContext對(duì)象。

UDT引用嵌入式代碼

代碼嵌入式UDF允許您將SQL腳本和第三方代碼放入同一個(gè)源碼文件,減少使用UDT的操作步驟,方便日常開發(fā)。詳情請(qǐng)參見UDF(嵌入式)