MaxCompute-ODPS重灌上陣 第三彈-複雜型別

振禹發表於2017-10-17

MaxCompute(原ODPS)是阿里雲自主研發的具有業界領先水平的分散式大資料處理平臺, 尤其在集團內部得到廣泛應用,支撐了多個BU的核心業務。 MaxCompute除了持續優化效能外,也致力於提升SQL語言的使用者體驗和表達能力,提高廣大ODPS開發者的生產力。

MaxCompute基於ODPS2.0新一代的SQL引擎,顯著提升了SQL語言編譯過程的易用性與語言的表達能力。我們在此推出MaxCompute(ODPS2.0)重灌上陣系列文章

第一彈 – 善用MaxCompute編譯器的錯誤和警告
第二彈 – 新的基本資料型別與內建函式
第三彈 – 複雜型別
第四彈 – CTE,VALUES,SEMIJOIN

上次向您介紹了 新的基本資料型別與內建函式,這次向您介紹複雜資料型別

原ODPS也支援兩種複雜型別,ARRAY, MAP,但是有些場景下還是不夠用

  • 場景1
    我的專案裡,生成的一箇中間表,為了優化效能,裡面有一列最好是個陣列,因為如果把陣列打散,每行上存一個元素,會因為其他列的重複導致資料量爆炸。首先想從上游表中生成這個陣列,搜尋半天文件,發現唯一的方式是把源資料列先轉STRING,再用wm_concat聚合,再用split函式打散成ARRAY<STRING>,這樣原來型別資訊丟了,不過STRING似乎也能用,好,繼續。後面的運算有個地方需要取陣列最後一個元素,試圖用陣列下標配合size函式,my_array[size(my_array)], 發現報告錯誤,下標必須是常量,可是我的陣列不是定長的,看看有沒有函式能反轉陣列呢?沒有!最後不得不放棄使用陣列。。。
  • 場景2
    我的任務是為每個廣告生成一個曲線,代表隨著廣告商的出價由低到高,預計的impression, click次數的曲線。最自然的表達是有個資料結構,裡面存著出價,impression次數,click次數。可是ODPS不支援這樣的用法,只好encode成一個字串,每次操作先編碼,再解碼。好麻煩,效率也很差,可是沒有辦法。。。

MaxCompute採用基於ODPS2.0的SQL引擎,大幅度改進了複雜型別並提供了配套的內建函式,基本解決了上述問題。

複雜型別的擴充

此文中採用MaxCompute Studio作展示 ( MaxCompute從2.8.0.2版本開始支援複雜型別,如果您的版本不夠新,請升級到最新版本 )。

首先,安裝MaxCompute Studio匯入測試MaxCompute專案,建立工程,建立一個新的MaxCompute指令碼檔案, 如下

image.png

執行後,可以在MaxCompute Studio的Project Explorer中找到新建立的表,察看錶的詳細資訊,並預覽資料,如下圖
image.png

可以看到MaxCompute支援ARRAY, MAP, STRUCT型別,並且可以任意巢狀使用。

MaxCompute Studio支援含新型別表資料的匯入匯出,可參考此ATA文章

  • MaxCompute支援的複雜型別見下表
型別 定義示例 構造示例 訪問示例
ARRAY array<int>,
array<struct<a:int, b:string>>
array(1, 2, 3),
array(array(1, 2), array(3, 4))
a[1],
a[x][y]
MAP map<string, string>,
map<tinyint, array<string>>
map("k1", "v1", "k2", "v2"),
map(1Y, array(`a`, `b`), 2Y, array(`x`, `y`))
m[`k1`],
m[2Y][id]
STRUCT struct<x:int, y:int>,
struct<a:array<int>, b:map<int, int>>
named_struct(`x`, 1, `y`, 2),
named_struct(`a`, array(1, 2), `b`, map(1, 7, 2, 8)
s.x,
s.b[1]

複雜型別構造與操作函式

返回型別 簽名 註釋
MAP<K, V> map(K key1, V value1, K key2, V value2, …) 使用給定key/value對建立map, 所有key型別一致,必須是基本型別,所有value型別一致,可為任意型別
ARRAY<K> map_keys(Map<K, V> m) 將引數中的map的所有key作為陣列返回,輸入NULL,返回NULL
ARRAY<V> map_values(MAP<K, V> m) 將引數中的map的所有value作為陣列返回,輸入NULL,返回NULL
int size(MAP<K, V>) 取得給定MAP元素數目
TABLE<K, V> explode(MAP<K, V>) 表生成函式,將給定MAP展開,每個key/value一行,每行兩列分別對應key和value
ARRAY<T> array(T value1, T value2, …) 使用給定value構造ARRAY,所有value型別一致
int size(ARRAY<T>) 取得給定ARRAY元素數目
boolean array_contains(ARRAY<T> a, value v) 檢測給定ARRAY a中是否包含v
ARRAY<T> sort_array(ARRAY<T>) 對給定陣列排序
ARRAY<T> collect_list(T col) 聚合函式,在給定group內,將col指定的表示式聚合為一個陣列
ARRAY<T> collect_set(T col) 聚合函式,在給定group內,將col指定的表示式聚合為一個無重複元素的集合陣列
TABLE<T> explode(ARRAY<T>) 表生成函式,將給定ARRAY展開,每個value一行,每行一列對應相應陣列元素
TABLE (int, T) posexplode(ARRAY<T>) 表生成函式,將給定ARRAY展開,每個value一行,每行兩列分別對應陣列從0開始的下標和陣列元素
STRUCT<col1:T1, col2:T2, …> struct(T1 value1, T2 value2, …) 使用給定value列表建立struct, 各value可為任意型別,生成struct的field的名稱依次為col1, col2, …
STRUCT<name1:T1, name2:T2, …> named_struct(name1, value1, name2, value2, …) 使用給定name/value列表建立struct, 各value可為任意型別,生成struct的field的名稱依次為name1, name2, …
TABLE (f1 T1, f2 T2, …) inline(ARRAY<STRUCT<f1:T1, f2:T2, …>>) 表生成函式,將給定struct陣列展開,每個元素對應一行,每行每個struct元素對應一列

在UDF中使用複雜型別

原ODPS不支援在UDF中訪問任何複雜型別。MaxCompute Java UDF支援所有複雜型別,Python UDF也會在不久的將來支援。

JAVA UDF中複雜型別的表示方法

ODPS的UDF分為三大類:UDF,UDAF,UDTF,其中:

  1. UDAF和UDTF通過@Resolve annotation來指定sinature,在MaxCompute 2.0上線後,使用者將可以在Resolve annotation中。如 @Resolve("array<string>,struct<a1:bigint,b1:string>,string->map<string,bigint>,struct<b1:bigint, b2:binary>")
  2. UDF通過evaluate方法的signature來對映UDF的輸入輸出型別,此時參考MaxCompute型別與java型別的對映關係。其中array對應java.util.List, map對應java.util.Map,struct對應com.aliyun.odps.data.Struct 。需要注意的是,com.aliyun.odps.data.Struct從反射是看不出field name和field type的,所以需要用 @Resolve annotation來輔助,即如果需要再UDF中使用struct,要求在UDF class上也標註上@Resolve註解,這個註解只會影響引數或返回值中包含com.aliyun.odps.data.Struct的過載。目前class上只能提供一個@Resolve annotation,因此一個UDF中帶有struct 引數或返回值的過載只能有一個。這個是目前的一個限制,我們正在改進,後續將會取消這一限制。

實際用例

如以下程式碼,定義了一個有三個overloads的UDF,其中第一個用了array作為引數,第二個用了map作為引數,第三個用了struct。由於第三個overloads用了struct作為引數或者返回值,因此要求必須要對UDF class打上@Resolve annotation,來指定struct的具體型別。

@Resolve("struct<a:bigint>,string->string")
public class UdfArray extends UDF {
  public String evaluate(List<String> vals, Long len) {
    return vals.get(len.intValue());
  }

  public String evaluate(Map<String,String> map, String key) {
    return map.get(key);
  }

  public String evaluate(Struct struct, String key) {
    return struct.getFieldValue("a") + key;
  }
}

使用者可以直接將複雜型別傳入UDF中:

create function my_index as `UdfArray` using `myjar.jar`;
select id, my_index(array(`red`, `yellow`, `green`), colorOrdinal) as color_name from colors;

小節

MaxCompute豐富了複雜型別的支援,可以更好的適應各種應用場景。MaxCompute仍將在相容性,表達能力等多方面持續改進型別系統。從本系列的下一篇起,開始介紹MaxCompute在SQL語言其他方面的改進!


相關文章