MaxComputeは、新世代のSQLエンジンに基づいたユーザー定義型 (UDT) を導入しています。 UDTを使用すると、SQL文でサードパーティのプログラミング言語のクラスまたはオブジェクトを参照して、メソッドを呼び出したり、データを取得したりできます。
概要
多くのSQLエンジンでサポートされているUDTは、MaxComputeのSTRUCTタイプに似ています。 MaxComputeでサポートされているUDTは、CREATE TYPEステートメントに似ています。 UDT にはフィールドとメソッドの両方が含まれています。 MaxComputeで新しいデータ型を定義するためにDDL文を使用する必要はありません。 代わりに、MaxComputeでは新しいデータ型をSQL文で直接参照できます。 次の例は、UDTの使用方法を示しています。
たとえば、SQL文でjava.langパッケージを呼び出すには、次のいずれかの方法を使用できます。
UDTを使用してjava.langを呼び出す
-- Enable new data types. The following example uses a new type of INTEGER (INT). set odps.sql.type.system.odps2=true; SELECT java.lang.Integer.MAX_VALUE;
Javaと同様に、java.langパッケージは省略できます。 したがって、上記のステートメントは次のステートメントと同等です。
set odps.sql.type.system.odps2=true; SELECT Integer.MAX_VALUE;
次の応答が返されます。
+-----------+ | max_value | +-----------+ | 2147483647 | +-----------+
ユーザー定義関数 (UDF) を使用してjava.langを呼び出す
コードを開発します。 次のコードは、UDFクラスを定義します。
package com.aliyun.odps.test; public class IntegerMaxValue extends com.aliyun.odps.udf.UDF { public Integer evaluate() { return Integer.MAX_VALUE; } }
UDFをJARパッケージにコンパイルし、パッケージをアップロードして関数を作成します。
add jar odps-test.jar; create function integer_max_value as 'com.aliyun.odps.test.IntegerMaxValue' using 'odps-test.jar';
SQL文で関数を呼び出します。
select integer_max_value();
この例では、UDTは、他のプログラミング言語を使用してSQL機能を拡張するための手順を簡素化します。
シナリオ
UDTは、次のシナリオに適しています。
MaxComputeでは提供されていないが、他のプログラミング言語で実装できる機能を使用したい場合。
たとえば、一部の機能を実装するには、組み込みのJavaクラスを1回呼び出すだけで済みます。 ただし、MaxComputeはこれらの機能を実装する方法を提供していません。 UDFを使用してこれらのタスクを実行すると、手順が複雑になります。
SQL文でサードパーティのライブラリを呼び出して、関連する機能を実装する場合。 このシナリオでは、UDFを使用すると、関数をUDF内でラップするのではなく、SQLステートメントでサードパーティのライブラリが提供する関数を直接使用できます。
サードパーティのプログラミング言語のソースコードをSQL文で直接呼び出す必要があります。 SELECT TRANSFORMを使用すると、スクリプトをSQL文に含めて、これらのSQL文を読みやすく、保守しやすくすることができます。 Javaなどの一部のプログラミング言語では、ソースコードはコンパイルされた後にのみ実行できます。 UDTを使用して、SQL文でこれらの言語のオブジェクトとクラスを参照できます。
制限事項
UDF、UDAF、またはUDTを使用して、次の種類のテーブルからデータを読み取ることはできません。
スキーマの進化を行うテーブル
複雑なデータ型を含むテーブル
JSONデータ型を含むテーブル
取引テーブル
実装
次の例は、UDTの実行方法を示しています。
-- Sample data.
@table1 := select * from values ('100000000000000000000') as t(x);
@table2 := select * from values (100L) as t(y);
-- Code logic.
-- Create an object by using the new method.
@a := select new java.math.BigInteger(x) x from @table1;
-- Call a static method.
@b := select java.math.BigInteger.valueOf(y) y from @table2;
-- Call an instance method.
select /*+mapjoin(b)*/ x.add(y).toString() from @a a join @b b;
-- The following result is displayed:
100000000000000000100
次の図にプロセスを示します。
このUDTには、M1、R2、およびJ3の3つのステージがあります。 MapReduceでJOIN
操作が使用されている場合、データを再シャッフルする必要があります。 その結果、データは複数の段階で処理される。 データを処理するプロセスと物理マシンは、さまざまな段階で異なります。
new java.math.BigInteger(x)
メソッドのみがM1ステージで呼び出されます。
java.math.BigInteger.valueOf(y)
とx.add(y).toString()
メソッドは、J3ステージで別々に呼び出されます。 これらのメソッドは、異なる段階で呼び出され、異なるプロセスおよび異なる物理マシンで実行されます。 UDTはこれらのステージをカプセル化して、すべてのステージが同じJava仮想マシン (JVM) に実装されているような効果を実現します。
上記の例は、サブクエリの結果がUDT列をサポートすることを示しています。 変数aによって取得されるx列は、組み込み型ではなくjava.math.BigInteger
型です。 UDTデータを別の演算子に転送し、そのメソッドを呼び出すことができます。 データシャッフルでUDTデータを使用することもできます。
特徴
UDTはJavaのみをサポートします。 デフォルトでは、SDK for JavaのすべてのクラスをUDTで参照できます。
説明JDKランタイム環境はJDK 1.8です。 JDK 1.8以降のバージョンはサポートされない場合があります。
UDTでは、JARパッケージをアップロードし、これらのパッケージを直接参照することもできます。 特定のフラグがUDTに提供される。
set odps.sql.session.resources
: 参照するリソースを指定します。 複数のリソースを指定し、コンマ (,) で区切ることができます。 例:set odps.sql.session.resources=foo.sh,bar.txt;
説明このフラグは、
SELECT TRANSFORM
ステートメントでリソースを指定するために使用されるフラグと同じように機能します。 したがって、このフラグは2つの機能を制御します。 たとえば、「概要」で説明した UDF JAR パッケージを参照するために UDT を使用できます。set odps.sql.type.system.odps2=true; set odps.sql.session.resources=odps-test.jar; -- Specify the JAR package that you want to reference. This package must be uploaded to the required project. select new com.aliyun.odps.test.IntegerMaxValue().evaluate();
odps.sql.session.java.imports
: デフォルトのJavaパッケージを指定します。 複数のパッケージを指定し、コンマ (,) で区切ることができます。 このフラグは、JavaのIMPORT
ステートメントに似ています。java.math.BigInteger
などのクラスパスを指定するか、*
を使用できます。Static import
はサポートされていません。たとえば、「概要」で説明した UDF JAR パッケージを参照するために UDT を使用できます。
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.*; -- Specify the default JAR package. select new IntegerMaxValue().evaluate();
UDTはリソースアクセスをサポートします。 MaxCompute SQLでは、静的メソッド
com.aliyun.odps.udf.impl.UDTExecutionContext.get()
を呼び出して、ExecutionContext
オブジェクトを取得できます。 次に、このオブジェクトを使用して現在のExecutionContext
クラスにアクセスし、ファイルやテーブルなどのリソースにアクセスできます。UDTは次の操作をサポートします。
new
メソッドを使用してオブジェクトを作成します。new
メソッドを使用して配列を作成します。 初期化リストを使用できます。 例:new Integer[] { 1, 2, 3 }
静的メソッドを含むメソッドを呼び出します。
静的フィールドを始めとしたフィールドにアクセスします。
説明パブリックメソッドとパブリックフィールドのみがサポート対象です。
UDTの識別子には、パッケージ、クラス、メソッド、およびフィールドの名前が含まれます。 すべての識別子は、大文字と小文字が区別されます。
匿名クラスとラムダ式には対応していません。
UDTは式で使用されます。 値を返さない関数は、式の中では呼び出せません。 この問題は、後のバージョンで解決されます。
UDTは次のデータ型をサポートします。
UDTは、
cast(1 as java.lang.Object)
などのSQL型変換をサポートしています。 UDTは、(Object)1
などのJava型変換をサポートしていません。Java データ型は、組み込みデータ型に対応付けられます。 マッピングは、UDTに適用することができる。 詳細は、「Java UDF」にあるデータ型の対応付けの表をご参照ください。
組み込み型がマップされているJava型のメソッドを直接呼び出すことができます。 例:
'123'.length() , 1L.hashCode()
UDTは、組み込み関数とUDFで使用できます。 たとえば、
chr(Long.valueOf('100'))
では、Long.valueOf
はjava.lang.Long
型の値を返します。CHR
組み込み関数は、組み込みBIGINTタイプをサポートしています。Javaプリミティブ型のデータは自動的にボクシング型に変換され、前述の2つのルールが適用されます。
説明特定の新規組み込みデータ型については、
set odps.sql.type.system.odps2=true;
を使用してこれらの型を宣言する必要があります。 そうしないと、エラーが発生します。次の型変換ルールがUDTに適用されます。
UDTオブジェクトは、暗黙的に基本クラスのオブジェクトに変換できます。
UDTオブジェクトは、基本クラスまたはサブクラスのオブジェクトに強制的に変換できます。
継承なしの2つのオブジェクト間のデータ型変換は、元の変換ルールに従います。 しかしながら、そのような変換は、データに対する変更をもたらし得る。 たとえば、
java.lang.Long
型のデータを強制的にjava.lang.Integer
型に変換できます。 この変換では、組み込みのBIGINT型をINT型に変換するために使用されるルールを使用します。 このプロセスは、データへの変更、さらにはデータ精度の損失をもたらす可能性がある。
説明UDTオブジェクトはディスクに保存できません。 つまり、DDL文はUDTをサポートしていないため、UDTオブジェクトをテーブルに
挿入
できません。 ただし、データ型を組み込み型の1つに暗黙的に変換できる場合は、UDTオブジェクトを含むテーブルを作成できます。 BINARYは組み込み型で、自動シリアル化をサポートしています。 byte[] 配列をディスクに保存できます。 保存されたbyte[] 配列は、BINARY型に逆シリアル化できます。 UDTを保存するには、シリアル化メソッドと逆シリアル化メソッドを呼び出して、データ型をBINARYに変換する必要があります。出力をUDTにすることはできません。 ただし、
toString()
メソッドを呼び出して、データ型をjava.lang.String
型に変換できます。これは、toString() メソッドがすべてのJavaクラスをサポートするためです。 このメソッドを使用して、デバッグ中に UDT データを確認できます。set odps.sql.udt.display.tostring=true;
フラグを追加して、MaxComputeがjava.util.Objects.toString(...)
メソッドを使用してすべての出力UDTデータを文字列に変換できるようにすることもできます。 これにより、デバッグが容易になる。 このフラグは通常、PRINTステートメントにのみ適用できるため、デバッグに使用されます。INSERT
文には適用できません。UDTはJavaジェネリックをサポートしています。 たとえば、コンパイラは、
java.util.Arrays.asList(new java.math.BigInteger('1'))
によって返された値が、パラメータタイプに基づいてjava.util.List<java.math.BigInteger>
タイプであると判断することができる。説明コンストラクター関数で型パラメーターを指定するか、
java.lang.Object
を使用する必要があります。 これはJavaと同じです。例えば、
new java.util.ArrayList(java.util.Arrays.asList('1', '2'))
の結果は、java.util.ArrayList<Object>
型である。new java.util.ArrayList<String>(java.util.Arrays.asList('1', '2'))
の結果は、java.util.ArrayList<String>
型である。
すべての演算子は、UDTの代わりにMaxCompute SQLのセマンティクスを使用します。
文字列の組み合わせ:
String.valueOf(1) + String.valueOf(2)
の結果は3です。 2つの文字列は暗黙的にDOUBLEタイプの値に変換され、合計されます。 Java文字列連結を使用して文字列を結合すると、結果は12になります。=
operations: SQL文の=
演算子は、比較演算子として使用されます。 ある表現を別の表現と比較するために使用されます。 2つのオブジェクトが同等かどうかを確認するには、Javaでequalsメソッドを呼び出す必要があります。=
演算子を使用して、2つのオブジェクトの等価性を検証することはできません。
UDTには、オブジェクトの平等性の明確な定義がありません。 これはデータの入れ替えが原因です。 オブジェクトは、異なるプロセスまたは物理マシン間で送信され得る。 オブジェクト送信中、オブジェクトは、2つの異なるオブジェクトとして参照され得る。 たとえば、オブジェクトが 2 台のマシンにシャッフルされてから再シャッフルされることがあります。 したがって、UDTを使用する場合は、
=
演算子の代わりにequals
メソッドを使用して、2つのオブジェクトの等価性を検証する必要があります。同じ行または列のオブジェクトは、何らかの方法で相関されます。 しかしながら、異なる行または列におけるオブジェクト間の相関は保証されない。
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.class
型であり、組み込みのINT型として使用できるためです。 これは、組み込み型と特定のJava型の両方のルールを適用します。UDTを使用して、SCALAR関数によって提供される機能を実装できます。 組み込み関数COLLECT_SETおよびその他の関数をUDTで使用して、集計関数およびテーブル値関数によって提供される機能を実装できます。
メリット
UDTは次の利点を提供します。
UDTは使いやすいです。 関数を定義する必要はありません。
UDTはすべてのJDK機能をサポートします。 これにより、SQLの柔軟性が向上します。
UDTコードは、SQLコードと同じファイルに格納できます。 これはコード管理を容易にする。
他のプログラミング言語のライブラリを直接参照し、他の言語で記述したコードを再利用できます。
オブジェクト指向フィーチャを作成できます。
改善される機能:
値を返さない関数と、転送されたデータを直接使用する関数を呼び出します。 転送されたデータを直接使用する関数の場合、戻り値は無視されます。 たとえば、Listインターフェイスで提供される
add
メソッドを呼び出すと、このメソッドは転送したリストを返します。匿名クラスとラムダ式を使用します。
UDTをシャッフルキーとして使用します。
Pythonなど、より多くのプログラミング言語をサポートします。
パフォーマンス
UDTは、UDFと同様の方法で実行される。 したがって、UDTのパフォーマンスはUDFのパフォーマンスとほぼ同じです。 最適化されたコンピューティングエンジンは、特定のシナリオでUDTのパフォーマンスを向上させます。
UDTオブジェクトを異なるプロセスで使用する場合は、シリアル化および逆シリアル化する必要があります。
JOIN
やAGGREGATE
など、データの入れ替えを必要としない操作を実行するためにUDTを使用する場合、シリアル化と逆シリアル化のオーバーヘッドは回避されます。UDTのランタイムは、リフレクションではなくCodegenに基づいています。 したがって、パフォーマンスの低下は発生しません。 単一の関数呼び出しで複数のUDTを実行できます。 上記の例では、
values[x].add(values[y]).divide(java.math.BigInteger.valueOf(2))
が1回だけ呼び出されます。 したがって、UDTの動作単位が小さくても、追加のインターフェイスオーバーヘッドは発生しない。
セキュリティ
UDFと同様に、UDTはJavaサンドボックスモデルによって制限されます。 制限付き操作を実行するには、操作のサンドボックス分離をキャンセルするか、サンドボックスホワイトリストへの参加を申請する必要があります。