スクリプトモード (スクリプトモード SQL) を使用すると、複数の SQL ステートメントを単一のユニットとしてコンパイルおよび送信できます。このプロセスでは、1 つの実行計画が生成され、スクリプトが一度キューに追加され、すべてのステートメントが単一のジョブで実行されます。このアプローチは、連携する複数のステートメントを必要とする ETL データパイプライン、定期的なバッチ処理、およびクエリオーケストレーションのシナリオに最適です。スクリプトモードは、デフォルトでアトミック実行を行う通常モードと、ステートメントを順次実行するステップバイステップ実行モードの 2 つの実行モードをサポートします。
ユースケース
-
複雑なステートメントの書き換え:深くネストされたサブクエリを、一連の読みやすいテーブル変数への代入に分割します。
-
複数ステートメントのパイプライン構築:論理的に関連するステートメントを単一のジョブに結合し、キューイングとスケジューリングのオーバーヘッドを削減します。通常モードでは、すべてのステートメントがアトミックに実行されます。ステップバイステップ実行モードでは、ステートメントが順次実行されるため、書き込み後の読み取りシナリオや他のプラットフォームからの移行に最適です。詳細については、「実行モード」をご参照ください。
構文
-
ステートメントの種類:スクリプトモードは、SET ステートメント、一部の DDL ステートメント、および DML ステートメントをサポートします。DESC や SHOW など、結果を表示するステートメントはサポートされていません。
-
ステートメントの順序:スクリプトは、
SET→DDL→DMLという固定順序に従う必要があります。各セクションには 0 個以上のステートメントを含めることができますが、異なるセクションのステートメントを混在させることはできません。
-- 1. SET
SET odps.sql.type.system.odps2=true;
[SET odps.stage.reducer.num=xxx;]
[SET odps.sql.step.script.mode=true;] -- ステップバイステップ実行モードを有効化します
[...]
-- 2. DDL
CREATE TABLE table1 xxx;
[CREATE TEMPORARY TABLE table2 xxx;] -- 一時テーブルを作成します
[...]
-- 3. DML&DQL
@var1 := SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM table3
[WHERE where_condition];
@var2 := SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM table4
[WHERE where_condition];
@var3 := SELECT [ALL | DISTINCT] var1.select_expr, var2.select_expr, ...
FROM @var1 JOIN @var2 ON ...;
INSERT OVERWRITE|INTO TABLE [PARTITION (partcol1=val1, partcol2=val2 ...)]
SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM @var3;
[@var4 := SELECT [ALL | DISTINCT] var1.select_expr, var1.select_expr, ... FROM @var1
UNION ALL | UNION
SELECT [ALL | DISTINCT] var2.select_expr, var2.select_expr, ... FROM @var2;
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
AS
SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM @var4;]
[...]
実行モード
スクリプトモードは 2 つの実行モードをサポートします。
|
動作 |
通常モード (デフォルト) |
ステップバイステップ実行モード |
|
有効化の方法 |
デフォルト。設定は不要です。 |
SET セクションに |
|
コンパイル |
すべての DML ステートメントが単一の実行計画にコンパイルされます。 |
各 DML ステートメントは個別の実行計画にコンパイルされます。注:数千行または多数の操作を持つ非常に複雑なスクリプトは、コンパイルメモリの制限を超える可能性があります。 |
|
実行 |
すべてのステートメントは、すべての入力データの準備が完了した後に開始される単一のジョブとしてアトミックに実行されます。いずれかのステートメントが失敗した場合、スクリプト全体が失敗し、すべての操作がロールバックされます。 |
DML ステートメントは順次実行されます。ステートメントが失敗した場合、以前に実行されたステートメントはロールバックされず、スクリプトを最初から再試行する必要があります。大きなスクリプトは小さなものに分割することを推奨します。 |
|
書き込み後の読み取り |
サポートされていません。スクリプト内で同じテーブルに書き込んだ後に読み取りを行うとエラーが発生します。 |
サポートされていますが、トランザクションテーブルまたはパーティションテーブルには対応していません。 |
|
一時テーブル |
サポートされていません。 |
サポートされています。 |
DDL構文
一時テーブルの作成
スクリプトモードでは、一時テーブルを作成して中間結果をキャッシュし、同じスクリプト内で再利用できます。この操作には書き込み後の読み取りパターンが必要であり、ステップバイステップ実行モードでのみサポートされます。
構文
CREATE TEMPORARY TABLE <table_name> (
<col_name> <data_type>, ...
)
[LIFECYCLE <days>]
[AS <select_statement>];
パラメーター
|
パラメーター |
説明 |
|
|
現在のスクリプト内でのみアクセス可能です。 |
|
|
オプション。テーブルが自動的に削除されるまでの保持日数。デフォルト:1。 |
|
|
オプション。作成時に SELECT ステートメントのデータでテーブルを初期化します。 |
制限
-
一時テーブルには、ステップバイステップ実行モードが必要です。スクリプトの SET セクションに
SET odps.sql.step.script.mode=true;を追加してください。 -
一時テーブルは、それが作成されたスクリプト内でのみアクセス可能です。
-
一時テーブルは、パーティションテーブルまたはトランザクションテーブルにすることはできません。
-
一時テーブルを明示的に削除するには、
DROP TABLEステートメントがスクリプトの最後のステートメントである必要があります。 -
単一のスクリプトで 20 を超える一時テーブルを作成することは避けてください。
標準テーブルの作成
スクリプトモードは、標準テーブルの作成をサポートしています。構文の詳細については、「CREATE TABLE」をご参照ください。
DML および DQL 構文
ステートメントの制限
-
結果表示ステートメント:スクリプトには、スタンドアロンの
SELECTステートメントなど、画面出力を生成するステートメントを 1 つしか含めることができません。複数含めるとエラーが発生します。このようなステートメントはスクリプトでは使用しないでください。 -
CREATE TABLE AS:このステートメントはスクリプトごとに 1 回しか使用できず、最後の実行可能なステートメントでなければなりません。最初にテーブルを作成してからデータを挿入することを推奨します。
-
混合書き込みモード:同じスクリプト内で同じテーブルに対して OVERWRITE と INTO の両方を使用することはできません。トランザクションテーブルと標準テーブルに対する DML 操作を混在させることはできません。
-
書き込み後の読み取り:通常モードでは、同じスクリプト内でテーブルに書き込んだ後に読み取りを行うとエラーが発生します。この問題を解決するには、テーブル変数を使用するか、ステップバイステップ実行モードに切り替えます。ステップバイステップ実行モードは書き込み後の読み取り操作をサポートしますが、トランザクションテーブルまたはパーティションテーブルには対応していません。詳細な例については、「使用例 3:通常モードでの書き込み後の読み取り」をご参照ください。
変数
構文
-- @を使用して変数を宣言します。サポートされる型には、TABLE および任意の MaxCompute データ型が含まれます。
@var1 <type>
-- := を使用して値を代入します。
@var1 := <select_statement>
使用上の注意
-
テーブル型の変数を、指定されたデータ型の変数に代入することはできません。たとえば、次の構文は許可されていません。
@a TABLE (name STRING); @a := SELECT 'tom'; @b STRING; @b := SELECT * FROM @a; -
変数は定数値を格納できます。
SELECT * FROM @varを使用して変数の値をスカラーに変換できます。定数は、次の例に示すように、単一行のテーブルに格納することもできます。変換構文の詳細については、「サブクエリ」をご参照ください。@a := SELECT 10; -- 定数10を代入します @b := SELECT key, value + (SELECT * FROM @a) FROM t2; -- @aをスカラーとして使用します SELECT * FROM @b;
IF ステートメント
スクリプトモードでは、IF ステートメントを使用して、条件に基づいて実行フローを制御します。
構文
-- 単一分岐
IF (condition) BEGIN
statements
END
-- 複数分岐
IF (condition) BEGIN
statements
END ELSE IF (condition2) BEGIN
statements
END ELSE BEGIN
statements
END
使用上の注意
-
分岐にステートメントが 1 つしか含まれていない場合、Java の
{ }と同様に、BEGINおよびENDキーワードはオプションです。 -
CREATE TABLE、ALTER TABLE、TRUNCATE TABLEなどの DDL ステートメントは、IF 分岐内ではサポートされていません。 -
条件は 2 つの型をサポートします。
-
ブール式:分岐はコンパイル時に決定されます。
-
ブールスカラサブクエリ:分岐は実行時に決定されます。MaxCompute が複数のジョブを送信する場合があります。
-
使用例 1: IF ステートメントでは、条件は BOOLEAN 型の式です。このようなIF ELSE ステートメントは、次の例に示すように、コンパイル時にどの分岐を実行するかを決定できます。
@date := '20190101';
@row TABLE(id STRING);
IF (CAST(@date AS BIGINT) % 2 == 0) BEGIN
@row := SELECT id FROM src1;
END ELSE BEGIN
@row := SELECT id FROM src2;
END
INSERT OVERWRITE TABLE dest SELECT * FROM @row;
使用例 2: IF ステートメントの条件は、ブールスカラサブクエリです。このようなIF ELSE ステートメントでは、実行分岐は実行時に決定されるため、MaxCompute が複数のジョブを送信する必要があります。
@i BIGINT;
@t TABLE(id BIGINT, value BIGINT);
IF ((SELECT COUNT(*) FROM src WHERE a = '5') > 1) BEGIN
@i := 1;
@t := SELECT @i, @i*2;
END ELSE BEGIN
@i := 2;
@t := SELECT @i, @i*2;
END
SELECT id, value FROM @t;
使用例
使用例 1:基本的な例
次のスクリプトは、3 つのソーステーブルのデータを結合および和集合し、その結果を 2 つの宛先テーブルに挿入します。すべてのステートメントは単一の DAG にコンパイルされ、アトミックに実行されます。
CREATE TABLE IF NOT EXISTS dest(key STRING, value BIGINT) PARTITIONED BY (d STRING);
CREATE TABLE IF NOT EXISTS dest2(key STRING, value BIGINT) PARTITIONED BY (d STRING);
@a := SELECT * FROM src WHERE value > 0;
@b := SELECT * FROM src2 WHERE key IS NOT NULL;
@c := SELECT * FROM src3 WHERE value IS NOT NULL;
@d := SELECT a.key, b.value FROM @a LEFT OUTER JOIN @b ON a.key = b.key AND b.value > 0;
@e := SELECT a.key, c.value FROM @a INNER JOIN @c ON a.key = c.key;
@f := SELECT * FROM @d UNION SELECT * FROM @e UNION SELECT * FROM @a;
INSERT OVERWRITE TABLE dest PARTITION (d='20171111') SELECT * FROM @f;
@g := SELECT e.key, c.value FROM @e JOIN @c ON e.key = c.key;
INSERT OVERWRITE TABLE dest2 PARTITION (d='20171111') SELECT * FROM @g;
使用例 2:ステップバイステップモードでの書き込み後の読み取り
次のスクリプトは、ステップバイステップ実行モードを有効にして、同じスクリプト内で書き込み後の読み取り操作を実行します。
SET odps.sql.step.script.mode=true;
DROP TABLE IF EXISTS foo_t1;
DROP TABLE IF EXISTS foo_t2;
CREATE TABLE foo_t1(a STRING) LIFECYCLE 1;
CREATE TABLE foo_t2(a STRING) LIFECYCLE 1;
@x := SELECT 'hello, world' AS a;
INSERT OVERWRITE TABLE foo_t1 SELECT * FROM @x;
INSERT INTO foo_t2 SELECT * FROM foo_t1 UNION ALL SELECT * FROM foo_t1;
SELECT * FROM foo_t2;
使用例 3:通常モードでの書き込み後の読み取り
通常モードでは、同じスクリプト内でテーブルに書き込んだ後に読み取りを行うとエラーが発生します。この例では、エラーと 2 つの可能な解決策を示します。
データ準備
CREATE TABLE src(key BIGINT, value BIGINT) LIFECYCLE 1;
CREATE TABLE src2(key BIGINT, value BIGINT) LIFECYCLE 1;
INSERT INTO src VALUES(1, 2), (3, 3);
エラーの例 (通常モードでの書き込み後の読み取り)
INSERT OVERWRITE TABLE src2 SELECT * FROM src WHERE key > 0;
@a := SELECT * FROM src2; -- エラー:src2に書き込んだ後に同じスクリプト内で読み取っています。
SELECT * FROM @a;
解決策 1: ステップバイステップ実行モードを有効にする
SET odps.sql.step.script.mode=true;
INSERT OVERWRITE TABLE src2 SELECT * FROM src WHERE key > 0;
@a := SELECT * FROM src2;
SELECT * FROM @a;
解決策 2: テーブル変数を使用するように SQL を書き換える
@a := SELECT * FROM src WHERE key > 0;
INSERT OVERWRITE TABLE src2 SELECT * FROM @a;
SELECT * FROM @a;
使用例 4:一時テーブルの作成と削除
次のスクリプトは、一時テーブルを作成して JOIN の中間結果をキャッシュし、それを 2 回読み取ってから削除します。
-- ステップバイステップ実行モードを有効化します (一時テーブルで必須)。
SET odps.sql.step.script.mode=true;
DROP TABLE IF EXISTS foo_t1;
CREATE TABLE foo_t1(a BIGINT, b BIGINT);
-- JOINを使用して一時テーブルを作成します。
CREATE TEMPORARY TABLE t AS
SELECT t1.a AS a, t2.d AS b FROM
(SELECT 1 a, 2 b) t1
JOIN
(SELECT 1 c, 10 d) t2
ON t1.a = t2.c;
INSERT INTO foo_t1 SELECT * FROM t UNION ALL SELECT * FROM t;
SELECT * FROM foo_t1;
-- 一時テーブルを削除します (スクリプトの最後のステートメントである必要があります)。
DROP TABLE t;
スクリプトの送信
スクリプトモードは、MaxCompute Studio、MaxCompute クライアント (odpscmd)、DataWorks、および Java SDK と Python SDK でサポートされています。
MaxCompute クライアント (odpscmd)
odpscmd v0.27 以降を使用してください。MaxCompute クライアントをインストールした後、-s フラグを使用してスクリプトを送信します。
myscript.sql などのスクリプトファイルを作成します。次に、コマンドラインから次のコマンドを実行して odpscmd を実行します。コマンドラインから MaxCompute クライアントを実行する方法の詳細については、「MaxCompute クライアントの実行」をご参照ください。
..\bin>odpscmd -s myscript.sql
スクリプトモードとテーブル変数は、odpscmd インタラクティブシェルではサポートされていません。-s フラグは、-f や-e と同様に、odpscmd のコマンドラインオプションです。インタラクティブ環境のコマンドではありません。
DataWorks
DataWorks では、ODPS Script ノードを使用してスクリプトを作成および実行します。その後、出力に表示される Logview URL を使用して実行計画と結果を表示できます。
左上隅で、[+ Create] > [Create Node] の順にクリックします。MaxCompute カテゴリで [ODPS Script] を選択します。
スクリプトを作成した後、ツールバーの実行 アイコンをクリックして実行します。出力ログには、実行計画と最終結果を確認するための Logview URL が表示されます。
SDK
Java SDK または Python SDK を使用して SQL スクリプトを直接実行できます。詳細については、「Java SDK」および「Python SDK」の概要をご参照ください。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.aliyun.odps.Instance;
import com.aliyun.odps.Odps;
import com.aliyun.odps.OdpsException;
import com.aliyun.odps.account.Account;
import com.aliyun.odps.account.AliyunAccount;
import com.aliyun.odps.data.Record;
import com.aliyun.odps.task.SQLTask;
public class SdkTest {
public static void main(String[] args) throws OdpsException {
// Alibaba Cloud AccessKeyペアは、アカウントへのフルアクセスを許可するため、高いリスクを伴います。
// APIの呼び出しや日常の運用には、RAMユーザーを作成して使用することを強く推奨します。
// RAMユーザーを作成するには、RAMコンソールにログインします。
// この例では、環境変数を使用して認証情報を保存する方法を示します。
// セキュリティのため、AccessKeyペアをコードにハードコーディングしないでください。
Account account = new AliyunAccount(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"), System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"));
Odps odps = new Odps(account);
odps.setDefaultProject("your project_name");
odps.setEndpoint("your end_point");
String sqlScript = "@a := SELECT * FROM jdbc_test;\n"
+ "SELECT * FROM @a;";
// 必須:スクリプトモードを有効にします。
Map<String, String> hints = new HashMap<>();
hints.put("odps.sql.submit.mode", "script");
Instance instance = SQLTask.run(odps, "your project_name", sqlScript, hints, null);
instance.waitForSuccess();
List<Record> recordList = SQLTask.getResult(instance);
for (Record record : recordList) {
System.out.println(record.get(0));
System.out.println(record.get(1));
}
}
}import os
from odps import ODPS
# Alibaba Cloud AccessKeyペアは、アカウントへのフルアクセスを許可するため、高いリスクを伴います。
# APIの呼び出しや日常の運用には、RAMユーザーを作成して使用することを強く推奨します。
# RAMユーザーを作成するには、RAMコンソールにログインします。
# この例では、環境変数を使用して認証情報を保存する方法を示します。
# セキュリティのため、AccessKeyペアをコードにハードコーディングしないでください。
o = ODPS(
os.environ["ALIBABA_CLOUD_ACCESS_KEY_ID"],
os.environ["ALIBABA_CLOUD_ACCESS_KEY_SECRET"],
"your project_name",
"your end_point"
)
sql_script = """
@a := SELECT * FROM jdbc_test;
SELECT * FROM @a;
"""
# 必須:スクリプトモードを有効にします。
hints = {"odps.sql.submit.mode": "script"}
instance = o.execute_sql(sql_script, hints=hints)
with instance.open_reader() as reader:
for rec in reader:
print(rec[0], rec[1])MaxCompute Studio
MaxCompute Studio でスクリプトを実行する前に、次の前提条件を完了してください。
スクリプトをコンパイルして実行すると、MaxCompute Studio に実行計画が表示されます。スクリプトには複数のステートメントが含まれていますが、実行計画は単一の DAG としてレンダリングされます。