このトピックでは、SELECT TRANSFORM ステートメントを使用して、MaxCompute SQL が直接サポートしていない操作を実行する方法について説明します。 具体的には、SELECT TRANSFORM を使用して指定した子プロセスを開始し、標準入力 (stdin) から必要な形式のデータを子プロセスに入力します。 次に、子プロセスの標準出力 (stdout) を解析して、最終出力を取得できます。 このプロセスでは、ユーザー定義関数 (UDF) をコンパイルする必要はありません。

サンプルコード
SELECT TRANSFORM(arg1, arg2 ...) 
(ROW FORMAT DELIMITED (FIELDS TERMINATED BY field_delimiter (ESCAPED BY character_escape)?)? 
(LINES SEPARATED BY line_separator)? 
(NULL DEFINED AS null_value)?)?
USING 'unix_command_line' 
(RESOURCES 'res_name' (',' 'res_name')*)? 
( AS col1, col2 ...)?
(ROW FORMAT DELIMITED (FIELDS TERMINATED BY field_delimiter (ESCAPED BY character_escape)?)? 
(LINES SEPARATED BY line_separator)? (NULL DEFINED AS null_value)?)?
パラメーター
  • キーワード SELECT TRANSFORM は、MAP または REDUCE に置き換えても意味は変わりません。 ただし、構文を明確にするため SELECT TRANSFORM の使用を推奨しています。
  • arg1,arg2... は、TRANSFORM 句の引数を示します。 TRANSFORM 句の引数の形式は、 SELECT 句の項目の形式に似ています。 デフォルトの形式では、各引数の式の結果は暗黙的に文字列に変換された後、\t を使用して結合され、指定された子プロセスに入力されます。 デフォルトの形式は変更が可能です。 詳細については、次のセクションで説明する ROW FORMAT 句をご参照ください。
  • USING:子プロセスを開始するコマンドを指定します。
    • ほとんどの MaxCompute SQL ステートメントでは、USING 句で指定することができるのはリソースのみです。 ただし、SELECT TRANSFORM ステートメントでは、Hive 構文との互換性を確保するため、USING 句でコマンドを指定することができます。
    • USING 句の形式は、シェルスクリプトの構文に似ています。 ただし、子プロセスを開始するためのシェルスクリプトは実際には実行されず、子プロセスはコマンド入力に従って作成されます。 したがって、入力と出力のリダイレクト、パイプ、ループなどの多くのシェル機能は使用できません。 シェルスクリプトは、必要に応じて子プロセスのコマンドとして使用することができます。
  • RESOURCES:指定された子プロセスがアクセスできるリソースを指定します。 次の 2 つの方法のいずれかを使用して、リソースを指定します。
    • RESOURCES 句を使用します。たとえば、using 'sh foo.sh bar.txt' Resources 'foo.sh','bar.txt' を使用します。
    • SQL ステートメントの前に set odps.sql.session.resources=foo.sh,bar.txt; 句を追加します。 この句が指定されるとグローバルに有効になります。すべての SELECT TRANSFORM ステートメントは、この句で指定されたリソースにアクセスできます。
  • ROW FORMAT:入力または出力形式を指定します。
    構文には 2 つの ROW FORMAT 句が含まれます。1 つは入力形式を指定し、もう 1 つは出力形式を指定します。 デフォルトでは、列の区切りには \t が使用され、行の区切りには \n が使用されます。Null \N で表されます。
    • field_delimitercharacter_escapeline_separator で使用できるのは 1 文字のみです。 文字列を指定すると、文字列の最初の文字が他の文字よりも優先されます。
    • Hive は各形式の構文を指定します。 MaxCompute では、inputRecordReader、outputRecordReader、Serde などの構文がサポートされています。 これらの形式を使用するには、SQL ステートメントの前に set odps.sql.hive.compatible=true; を追加して、Hive との互換性を有効にする必要があります。 Hive でサポートされている構文の詳細については、「Hive」をご参照ください。
    • Hive でサポートされている inputRecordReader や outputRecordReader などの構文を指定すると、ステートメントの実行速度が低下する場合があります。
  • AS:出力列を指定します。
    • AS 句でデータ型を指定できます (例: as(col1:bigint, col2:boolean)as(col1, col2) など、データ型を指定しない場合、デフォルトで文字列が返されます。
    • 出力は、子プロセスの標準出力を解析することにより取得します。 指定されたデータ型に STRING が含まれていない場合、システムは CAST 関数を暗黙的に呼び出しますが、ランタイム例外が発生する場合があります。
    • たとえば、as(col1, col2:bigint) のように、指定した列の一部のみにデータ型を指定することはできません。
    • AS 句をスキップすると、標準出力の最初の \t の前のフィールドがキーになり、後続のすべてのパートが値になり、as(key, value) と同等になります。

シェルスクリプトを呼び出す

シェルスクリプトを実行して 1 から 50 までの 50 行のデータを生成する場合、 data フィールドの出力は次のとおりです。
SELECT  TRANSFORM(script) USING 'sh' AS (data) 
FROM (
        SELECT  'for i in `seq 1 50`; do echo $i; done' AS script
      ) t
;

シェルコマンドは、TRANSFORM 句の入力として使用されます。

SELECT TRANSFORM は、単なる言語拡張機能ではありません。 AWK、Python、Perl、Shell などの単純な関数を使用すると、コマンドでスクリプトをコンパイルできるため、独立したスクリプトファイルをコンパイルしたり、リソースをアップロードしたりする必要がありません。

Python スクリプトを呼び出す

Python はコマンドでスクリプトをコンパイルできる単純な関数であるため、独立したスクリプトファイルをコンパイルしたり、リソースをアップロードしたりする必要はありません。 以下に、Python スクリプトを呼び出す方法の例を示します。

  1. Python スクリプトファイルをコンパイルします。 この例では、ファイル名は myplus.py です。
    #!/usr/bin/env python
    import sys
    line = sys.stdin.readline()
    while line:
        token = line.split('\t')
        if (token[0] == '\\N') or (token[1] == '\\N'):
            print '\\N'
        else:
            print int(token[0]) + int(token[1])
        line = sys.stdin.readline()
  2. Python スクリプトファイルをリソースとして MaxCompute に追加します。
    add py ./myplus.py -f;
    DataWorks コンソールを使用してリソースを追加することができます。
  3. SELECT TRANSFORM を使用して、リソースを呼び出します。
    Create table testdata(c1 bigint,c2 bigint); - Creates a test table.
    insert into Table testdata values (1,4),(2,5),(3,6); - Inserts test data into the test table.
    
    - Execute the SELECT TRANSFORM statement: 
    SELECT 
    TRANSFORM (testdata.c1, testdata.c2) 
    USING 'python myplus.py'resources 'myplus.py' 
    AS (result bigint) 
    FROM testdata;
    
    - Or:
    set odps.sql.session.resources=myplus.py;
    SELECT TRANSFORM (testdata.c1, testdata.c2) 
    USING 'python myplus.py' 
    AS (result bigint) 
    FROM testdata;
返される情報は以下のとおりです。
+-----+
| cnt |
+-----+
| 5   |
| 7   |
| 9   |
+-----+

Python スクリプトでは、 MaxCompute をPython フレームワークで実行する必要がないため、フォーマット要件の対象ではありません。

MaxCompute では、Python コマンドを TRANSFORM 句の入力として使用することができます。 たとえば、Python コマンドを実行してシェルスクリプトを呼び出すことができます。
SELECT  TRANSFORM('for i in xrange(1, 50):  print i;') USING 'python' AS (data);

Java スクリプトを呼び出す

Java スクリプトの呼び出しは、Python スクリプトの呼び出しに似ています。 この例では、Java スクリプトファイルをコンパイルし、.jar パッケージとしてエクスポートした後 add コマンドを実行して、.jar パッケージをリソースとして MaxCompute に追加する必要があります。 リソースは、SELECT TRANSFORM を使用して呼び出されます。

  1. Java スクリプトファイルをコンパイルし、.jarパッケージとしてエクスポートします。 この例では、.jar パッケージの名前は Sum.jar です。
    package com.aliyun.odps.test;
    import java.util.Scanner
    public class Sum {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            while (sc.hasNext()) {
                String s = sc.nextLine();
                String[] tokens = s.split("\t");
                if (tokens.length < 2) {
                    throw new RuntimeException("illegal input");
                }
                if (tokens[0].equals("\\N") || tokens[1].equals("\\N")) {
                    System.out.println("\\N");
                }
                System.out.println(Long.parseLong(tokens[0]) + Long.parseLong(tokens[1]));
            }
        }
    }
  2. リソースとして .jar パッケージを MaxCompute に追加します。
    add jar ./Sum.jar -f;
  3. SELECT TRANSFORM を使用して、リソースを呼び出します。
    Create table testdata(c1 bigint,c2 bigint); - Creates a test table.
    insert into Table testdata values (1,4),(2,5),(3,6); - Inserts test data into the test table.
    - Execute the SELECT TRANSFORM statement: 
    SELECT TRANSFORM(testdata.c1, testdata.c2) USING 'java -cp Sum.jar com.aliyun.odps.test.Sum' resources 'Sum.jar' from testdata;
    - Or:
    set odps.sql.session.resources=Sum.jar;
    SELECT TRANSFORM(testdata.c1, testdata.c2) USING 'java -cp Sum.jar com.aliyun.odps.test.Sum' FROM testdata;
返される情報は以下のとおりです。
+-----+
| cnt |
+-----+
| 5   |
| 7   |
| 9   |
+-----+
前述のメソッドを使用することで、ほとんどの Java ユーティリティを実行することができます。
Java および Python 用のユーザー定義テーブル値関数 (UDTF) フレームワークが提供されていますが、SELECT TRANSFORM を使用してコードをコンパイルする方がより簡単です。 SELECT TRANSFORM は、形式要件がなく、オフラインで呼び出すことができるため、より簡単なプロセスです。 Java および Python オフラインスクリプトのパスは、それぞれ JAVA_HOME および PYTHON_HOME 環境変数から取得できます。

他の言語のスクリプトを呼び出す

上記の言語拡張に加えて、SELECT TRANSFORM は、AWK や Perl などの一般的に使用される Unix コマンドおよびスクリプトインタープリターをサポートしています。

AWK を呼び出して列 2 を出力する方法の例
SELECT TRANSFORM(*) USING "awk '//{print $2}'" as (data) from testdata;
Perl を呼び出す例
SELECT TRANSFORM (testdata.c1, testdata.c2) USING "perl -e 'while($input = <STDIN>){print $input;}'" FROM testdata;
現在、PHP と Ruby は MaxCompute クラスターにデプロイされていません。 そのため、MaxCompute では、PHP または Ruby スクリプトを呼び出すことはできません。

並行してスクリプトを呼び出す

SELECT TRANSFORM を使用すると、スクリプトを順番に呼び出すことができます。 たとえば、データの前処理には、distribute by および sort by が使用できます。
SELECT TRANSFORM(key, value) USING 'cmd2' from 
(
    SELECT TRANSFORM(*) USINg 'cmd1' from 
    (
        SELECt * FROM data distribute by col2 sort by col1
    ) t distribute by key sort by value
) t2;
map またはreduce を使用しても同じ結果が得られます。
@a := select * from data distribute by col2 sort by col1;
@b := map * using 'cmd1' distribute by col1 sort by col2 from @a;
reduce * using 'cmd2' from @b;

SELECT TRANSFORM のパフォーマンス

SELECT TRANSFORM および UDTF のパフォーマンスは、シナリオによって異なります。 一般に、SELECT TRANSFORM のパフォーマンスはデータサイズが比較的小さいときに最適であり、UDTF のパフォーマンスはデータサイズが大きいときに最適です。

SELECT TRANSFORM は使いやすいため、アドホックデータ分析に適しています。

UDTF の利点
  • UDTF では、出力と入力は指定されたデータ型に従う必要があるため、UDTF は SELECT TRANSFORM で必要になるデータ型変換を必要としません。
  • UDTF では、SELECT TRANSFORM と同様に、オペレーティングシステムパイプが空であるか、完全に使用されている場合でもプロセスは中断されません。
  • UDTF では、定数パラメーターを送信する必要はありません (SELECT TRANSFORM では必要です)。
SELECT TRANSFORM の利点
  • SELECT TRANSFORM は子プロセスと親プロセスをサポートしているため、サーバーの複数のコアを活用することができます。
  • SELECT TRANSFORM は、基礎となるシステムを呼び出して、送信するデータを読み書きします。これにより、Java よりも高いパフォーマンスを実現できます。
  • SELECT TRANSFORM は、AWK などのツールをサポートし、ネイティブコードを実行することができます。 このため、SELECT TRANSFORM には Java と比較してより多くの利点があります。