MaxCompute は、新世代 SQL エンジンをベースにしたユーザー定義型 (UDT) を導入しています。 UDT を使用すると、SQL ステートメントでサードパーティのプログラミング言語のクラスやオブジェクトを参照して、メソッドを呼び出したり、データを取得したりできます。
UDT と UDF の使い分け
UDT とユーザー定義関数 (UDF) はどちらも、カスタムロジックで MaxCompute SQL を拡張します。 ワークフローに基づいて選択してください。
| 状況 | 推奨されるアプローチ |
|---|---|
組み込みの Java クラスメソッド (例: Integer.MAX_VALUE) を直接呼び出す | UDT — 関数定義は不要 |
| SQL 式でサードパーティライブラリを直接再利用する | UDT — ラップせずにクラスをインラインで参照 |
| 複数ステージのジョブにわたって、コンパイルされたソース言語のオブジェクトを含める | UDT — ステージ間の JVM 状態を自動的にカプセル化 |
| 複数のプロジェクトにわたって再利用可能なビジネスロジックを実装する | UDF — 明示的な関数登録により共有可能になる |
ユースケース
関数を定義せずに Java 標準ライブラリのメソッドを呼び出す。 タスクで MaxCompute SQL がネイティブに公開していない組み込みの Java クラスメソッドが必要な場合、UDT を使用すると式で直接呼び出すことができます。
サードパーティライブラリをインラインで参照する。 サードパーティライブラリの関数を UDF 内にラップする代わりに、SQL ステートメントでクラスを直接参照します。
コンパイル済みソースコードを SQL に埋め込む。 Java のようにコンパイルが必要な言語の場合、UDT を使用すると、別の登録ステップなしで SQL 式のオブジェクトとクラスを参照できます。 スクリプトベースの代替手段については、「SELECT TRANSFORM」をご参照ください。
前提条件
UDT を使用する前に、以下を確認してください。
ご利用の環境で JDK 1.8 が利用可能であること。 JDK 1.8 より後のバージョンはサポートされていない場合があります。
INT などの型を使用する場合は、新しいデータ型が有効になっていること:
set odps.sql.type.system.odps2=true;
仕組み
他の SQL エンジン (通常は STRUCT 型に似た型エイリアスを定義する) の UDT とは異なり、MaxCompute の UDT は CREATE TYPE ステートメントのように機能します。フィールドとメソッドの両方を含み、DDL を記述することなく SQL で直接参照します。
次の例で違いを説明します。 Java の java.lang パッケージから Integer.MAX_VALUE にアクセスするには、次のようにします。
UDT の使用 (直接参照):
-- 新しいデータ型を有効にします (INTEGER などの型に必要です)。
set odps.sql.type.system.odps2=true;
SELECT java.lang.Integer.MAX_VALUE;java.lang は (Java と同様に) 自動的にインポートされるため、これは次と同じです。
set odps.sql.type.system.odps2=true;
SELECT Integer.MAX_VALUE;結果:
+-----------+
| max_value |
+-----------+
| 2147483647 |
+-----------+UDF の使用 (比較のため):
UDF クラスを記述します。
package com.aliyun.odps.test; public class IntegerMaxValue extends com.aliyun.odps.udf.UDF { public Integer evaluate() { return Integer.MAX_VALUE; } }コンパイル、アップロード、登録:
add jar odps-test.jar; create function integer_max_value as 'com.aliyun.odps.test.IntegerMaxValue' using 'odps-test.jar';呼び出し:
select integer_max_value();
UDT を使用すると、これを単一の SQL ステートメントに短縮できます。
複数ステージの実行
UDT オブジェクトは、MapReduce ステージ間で自然にフローします。 次の例では、異なるデータソースから計算された 2 つの BigInteger 列を結合します。
-- サンプルデータ
@table1 := select * from values ('100000000000000000000') as t(x);
@table2 := select * from values (100L) as t(y);
-- new メソッドでオブジェクトを作成
@a := select new java.math.BigInteger(x) x from @table1;
-- 静的メソッドを呼び出す
@b := select java.math.BigInteger.valueOf(y) y from @table2;
-- JOIN をまたいでインスタンスメソッドを呼び出す
select /*+mapjoin(b)*/ x.add(y).toString() from @a a join @b b;
-- 出力:
100000000000000000100このジョブは 3 つのステージ (M1、R2、J3) にわたって実行されます。 new java.math.BigInteger(x) は M1 で実行され、java.math.BigInteger.valueOf(y) と x.add(y).toString() は J3 で異なるプロセスと物理マシン上で実行されます。 UDT はこれをカプセル化するため、すべてのステージが同じ Java 仮想マシン (JVM) 上で実行されているかのように動作します。

変数 a の x 列は、組み込み型ではなく java.math.BigInteger 型です。 この UDT 値は、他の演算子に渡したり、データ再シャッフルで使用したりできます。
JAR パッケージの参照と Java インポートの設定
デフォルトでは、すべての SDK for Java クラスが UDT で利用できます。 追加の JAR パッケージを参照したり、デフォルトのインポートパスを設定したりするには、次のセッションフラグを使用します。
JAR パッケージの参照:
set odps.sql.type.system.odps2=true;
set odps.sql.session.resources=odps-test.jar;
-- JAR は事前にプロジェクトにアップロードしておく必要があります。
select new com.aliyun.odps.test.IntegerMaxValue().evaluate();複数のリソースをカンマで区切って指定できます: set odps.sql.session.resources=foo.sh,bar.txt;
odps.sql.session.resources は、UDT と SELECT TRANSFORM の両方を制御します。 ここで設定された JAR は、両方の機能で利用できます。デフォルトの Java インポートパスの設定:
set odps.sql.type.system.odps2=true;
set odps.sql.session.resources=odps-test.jar;
set odps.sql.session.java.imports=com.aliyun.odps.test.*;
-- インポートが設定されている場合、完全なパッケージプレフィックスを省略できます。
select new IntegerMaxValue().evaluate();odps.sql.session.java.imports は、クラスパス (例: java.math.BigInteger) またはワイルドカード (*) を受け入れます。 静的インポートはサポートされていません。
サポートされる操作
UDT は SQL 式で次の操作をサポートします。
newを使用したオブジェクトの作成 — 例:new java.math.BigInteger('123')初期化リスト付きの
newを使用した配列の作成 — 例:new Integer[] { 1, 2, 3 }インスタンスメソッドと静的メソッドの呼び出し
パブリックなインスタンスフィールドと静的フィールドへのアクセス
パブリックメソッドとパブリックフィールドのみがアクセス可能です。 すべての識別子 (パッケージ名、クラス名、メソッド名、フィールド名) は大文字と小文字を区別します。 匿名クラスとラムダ式はサポートされていません。 戻り値のない関数は式で呼び出すことはできません。
データ型
型マッピング
Java データ型は MaxCompute の組み込み型にマッピングされます。 Java UDF で使用されるのと同じマッピングが UDT にも適用されます。
組み込み型のメソッドを直接呼び出す:
'123'.length()、1L.hashCode()組み込み関数で UDT を使用する:
chr(Long.valueOf('100'))—Long.valueOfは、組み込みの BIGINT 型にマッピングされるjava.lang.Longを返しますJava のプリミティブ型は、対応するボクシング型に自動的に変換されます
新しい組み込みデータ型の場合は、クエリを実行する前に set odps.sql.type.system.odps2=true; を追加してください。
型変換
SQL の型変換がサポートされています:
cast(1 as java.lang.Object)Java スタイルのキャストはサポートされていません:
(Object)1UDT オブジェクトは基底クラスのオブジェクトに暗黙的に変換できます
UDT オブジェクトは基底クラスまたはサブクラスのオブジェクトに明示的に変換 (キャスト) できます
2 つの無関係な型間の変換は、組み込み型の変換と同じルールに従います。 たとえば、
java.lang.Longをjava.lang.Integerに変換すると、BIGINT を INT に変換するのと同じルールが適用され、データ損失が発生する可能性があります。
UDT オブジェクトはディスクに保存できず、テーブルに直接挿入することもできません (DDL は列の型として UDT をサポートしていません)。 UDT 値を組み込み型に暗黙的に変換できる場合は、テーブルに書き込むことができます。 BINARY は自動シリアル化をサポートしており、byte[]配列を保存して逆シリアル化できます。 UDT を永続化するには、シリアル化および逆シリアル化メソッドを使用して BINARY に変換します。 UDT 値を最終出力に直接表示することはできません。toString()を呼び出して、表示用に任意の UDT をjava.lang.Stringに変換します。 デバッグ中にすべての UDT 出力を自動的に文字列に変換するには、次を使用します。 このフラグは PRINT ステートメントにのみ適用され、INSERT ステートメントには適用されません。
set odps.sql.udt.display.tostring=true;ジェネリクス
UDT は Java のジェネリクスをサポートしています。 コンパイラは引数から型パラメーターを推論します。
-- java.util.List<java.math.BigInteger> を返します
java.util.Arrays.asList(new java.math.BigInteger('1'))コンストラクター呼び出しで型パラメーターを明示的に指定するか、java.lang.Object を使用します。
-- ArrayList<Object>
new java.util.ArrayList(java.util.Arrays.asList('1', '2'))
-- ArrayList<String>
new java.util.ArrayList<String>(java.util.Arrays.asList('1', '2'))演算子のセマンティクス
すべての演算子は、Java のセマンティクスではなく、MaxCompute SQL のセマンティクスに従います。
文字列連結:
String.valueOf(1) + String.valueOf(2)は3を返します (両方の文字列が暗黙的に DOUBLE にキャストされて合計されるため)。 文字列として連結するには、代わりに文字列連結関数を使用してください。等価性:
=演算子は SQL 比較演算子であり、Java の参照の等価性ではありません。 2 つのオブジェクトが等価であるかどうかを確認するには、equalsメソッドを使用してください。
オブジェクトの等価性とデータ再シャッフル
UDT にはオブジェクトの等価性に関する明確な定義がありません。 データ再シャッフルの際、オブジェクトはプロセスや物理マシンを越えて転送される可能性があり、その結果、単一のオブジェクトが 2 つの異なる参照として現れることがあります。 UDT オブジェクトを比較するには、常に equals メソッドを使用し、= は使用しないでください。
同じ行または列内のオブジェクトは相関していますが、行または列をまたいだ相関は保証されません。
制限事項
UDT は、JOIN、GROUP BY、DISTRIBUTE BY、SORT BY、ORDER BY、または CLUSTER BY 句でシャッフルキーとして使用することはできません。 UDT はこれらのステージの式では有効ですが、出力にすることはできません。 例:
group by new java.math.BigInteger('123')— サポートされていませんgroup by new java.math.BigInteger('123').hashCode()—hashCode()が組み込みの INT 型にマッピングされるint.classを返すため、サポートされています
UDF、ユーザー定義集計関数 (UDAF)、および UDT は、次のテーブルタイプからデータを読み取ることはできません。
スキーマ進化が実行されたテーブル
複雑なデータ型を含むテーブル
JSON データ型を含むテーブル
トランザクションテーブル
リソースへのアクセス
MaxCompute SQL では、静的メソッド com.aliyun.odps.udf.impl.UDTExecutionContext.get() を呼び出して ExecutionContext オブジェクトを取得します。 このオブジェクトを使用して、リソースとして登録されているファイルやテーブルなど、現在の実行コンテキストにアクセスします。
パフォーマンスに関する考慮事項
UDT のパフォーマンスは UDF のパフォーマンスと似ています。 最適化されたコンピューティングエンジンは、特定のシナリオで追加の改善を提供します。
ローカル操作でのシリアル化オーバーヘッドなし。 UDT オブジェクトが同じプロセス内で使用される場合 (JOIN や AGGREGATE ステージなど、データ再シャッフルが不要な場合)、シリアル化と逆シリアル化はスキップされます。
Codegen ベースのランタイム。 UDT はリフレクションではなく Codegen を介して実行されるため、リフレクションのオーバーヘッドはありません。 複数の UDT 呼び出しは単一の関数呼び出しにバッチ処理されます。たとえば、
values[x].add(values[y]).divide(java.math.BigInteger.valueOf(2))は一度だけ呼び出され、呼び出しごとのインターフェイスのオーバーヘッドを回避します。
セキュリティ
UDT は UDF と同じ Java サンドボックスモデルに従います。 サンドボックスによって制限されている操作を実行するには、それらの操作のサンドボックス分離をキャンセルするか、サンドボックスのホワイトリストへの追加を申請してください。
改善予定の機能
次の機能は将来のバージョンで計画されています。
戻り値のない関数、および転送されたデータを直接使用する関数 (戻り値が無視される場合、List インターフェイスの
addメソッドなど) の呼び出し。匿名クラスとラムダ式の使用。
シャッフルキーとしての UDT の使用。
Python などのより多くのプログラミング言語のサポート。
次のステップ
Java UDF — データ型マッピングテーブルと UDF 実装リファレンス
新しい組み込みデータ型 —
set odps.sql.type.system.odps2=true;が必要な型SELECT TRANSFORM — SQL ステートメントにスクリプトを埋め込む
COLLECT_SET およびその他の集計関数 — UDT と使用して、集計関数およびテーブル値関数の動作を実装します
Java サンドボックス — サンドボックスモデルとホワイトリスト申請