全部產品
Search
文件中心

MaxCompute:SQL指令碼模式

更新時間:Jun 06, 2026

指令碼模式(Script Mode SQL)將多條 SQL 陳述式作為一個整體編譯和提交,產生一份執行計畫,只需排隊一次、執行一次。適用於 ETL 資料管道、周期性批處理,以及需要多條語句協同完成的查詢編排情境。指令碼模式支援普通模式(預設,原子性執行)和逐步執行模式(逐條串列執行)。

  • 指令碼模式不支援費用預估,實際費用以賬單為準。詳情請參見查看賬單詳情

  • 單個指令碼最多引用 10,000 張表。每次引用獨立計數,包括對同一張表的重複引用和視圖定義中引用的表。

  • 如果多個輸入的資料來源資料準備完成的時間間隔很長(例如一個01:00可以準備好,一個07:00可以準備好),則不適合通過table variable銜接拼裝為一個大的指令碼模式SQL。

適用情境

  • 複雜單語句改寫:將深度嵌套的子查詢拆分為一系列可讀的表變數指派陳述式。

  • 多語句管道:將邏輯上相關的多條語句合并為一個作業統一提交,減少排隊和調度開銷。普通模式下所有語句原子性執行;逐步執行模式下語句逐條串列執行,適用於需要先寫後讀或跨平台遷移的情境。詳情請參見執行模式

文法結構

  • 語句類型:指令碼模式支援SET語句、部分DDL語句(不支援結果為屏顯類型的語句如DESC、SHOW)、DML語句。

  • 語句順序:指令碼的完整形式是SETDDLDML 的固定順序,每個語句類型都可以包含0到多個具體的SQL語句,但是不同類型的語句塊不能混用。

-- 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;]
[...]

執行模式

指令碼模式支援兩種執行模式。

行為

普通模式(預設)

逐步執行模式

開啟方式

預設,無需額外配置。

在 SET 部分添加 SET odps.sql.step.script.mode=true;

編譯

所有 DML 語句編譯為一個執行計畫。

每條 DML 語句獨立編譯為一個執行計畫。注意:非常複雜的指令碼(例如上千行、包含大量操作)可能超出編譯記憶體限制。

執行

所有語句作為一個作業原子執行,所有輸入資料就緒後才開始。任一語句失敗,整個指令碼失敗,所有操作復原。

DML 語句按順序逐條執行。某條語句失敗時,已執行的語句不會復原,只能從頭重試。建議將大指令碼拆分為多個較小指令碼。

先寫後讀

不支援。在同一指令碼中對同一張表先寫入再讀取會報錯。

支援。目前不支援事務表和分區表。

暫存資料表

不支援。

支援。

DDL 文法

建立暫存資料表

指令碼模式下支援建立暫存資料表,緩衝中間結果,供同一指令碼內複用。此時由於需要先寫後讀,僅支援在逐步執行模式下運行。

文法

CREATE TEMPORARY TABLE <table_name> (
  <col_name> <data_type>, ...
)
[LIFECYCLE <days>]
[AS <select_statement>];

參數說明

參數

說明

table_name

暫存資料表名稱。僅在當前指令碼內可訪問。

LIFECYCLE <days>

可選。表自動刪除前的保留天數。預設值:1。

AS <select_statement>

可選。建立時通過 SELECT 語句填充表資料。

使用限制

  • 暫存資料表需要開啟逐步執行模式。在指令碼的 SET 部分添加 SET odps.sql.step.script.mode=true;

  • 暫存資料表僅在建立它的指令碼內可訪問。

  • 暫存資料表不能是分區表或事務表。

  • 如需顯式刪除暫存資料表,DROP TABLE 語句必須放在指令碼末尾。

  • 同一個指令碼中建立的暫存資料表不建議超過20個。

建立普通表

指令碼模式支援建立普通表。文法詳情請參見CREATE TABLE

DML 和 DQL 文法

語句限制

  • 屏顯語句:單個指令碼中僅允許包含一條有屏顯結果輸出的語句(如單獨的 SELECT語句),超出將觸發報錯。建議避免在指令碼中使用此類語句

  • CREATE TABLE AS:每個指令碼僅允許一次,且必須是最後一條可執行語句。建議先建立表再插入資料。

  • 混合寫入模式:同一指令碼中不支援同一張表同時使用 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將其轉換為標量,常量值也可以存放在一個單行的表中,命令樣本如下。轉化文法請參見子查詢(SUBQUERY)

    @a := SELECT 10;                                         -- 賦值常量 10
    @b := SELECT key, value + (SELECT * FROM @a) FROM t2;   -- 將 @a 作為標量使用
    SELECT * FROM @b;

IF 語句

指令碼模式下,支援IF語句:IF語句可以使程式根據條件,自動選擇執行邏輯。

文法

-- 單分支
IF (condition) BEGIN
  statements
END
-- 多分支
IF (condition) BEGIN
  statements
END ELSE IF (condition2) BEGIN
  statements
END ELSE BEGIN
  statements
END

使用說明

  • 當分支只包含一條語句時,關鍵字 BEGINEND 可省略(類似 Java 中的 { })。

  • IF 分支內不支援 CREATE TABLEALTER TABLETRUNCATE TABLE 等 DDL 語句。

  • condition 支援兩種類型:

    • 布林運算式:編譯期確定分支。

    • 布爾標量子查詢:運行期確定分支,MaxCompute 可能提交多個作業。

樣本1:IF語句中的 condition為 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語句中的 condition 為 BOOLEAN 的 Scalar SubQuery。這種類型的IF ELSE語句在編譯階段無法決定執行哪個分支,在運行時才能決定。因此,需要提交多個作業,樣本如下:

@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:基礎樣本

以下指令碼對三張源表進行 JOIN 和 UNION 操作,將結果插入兩張目標表。所有語句編譯為一個 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:普通模式下的先寫後讀

普通模式下在同一指令碼中先寫入表再讀取會報錯。以下樣本展示該錯誤及兩種解決方案。

資料準備

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 中間結果,讀取兩次後刪除。

-- 開啟逐步執行模式(暫存資料表必須)
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
說明

-s為odpscmd的命令列選項,類似於-f-e,而非互動環境中的命令。odpscmd的互動環境中暫不支援指令碼模式與表變數。

通過DataWorks使用指令碼模式

在 DataWorks 中建立 ODPS Script 節點。在節點編輯器中編寫指令碼,點擊運行表徵圖提交到 MaxCompute。通過輸出面板中的 Logview URL 查看執行計畫和結果。

單擊左上方 + Create > Create Node,在 MaxCompute 分類下選擇 ODPS Script

在此節點中進行指令碼模式編輯,編輯完成後單擊工具列的运行表徵圖,提交指令碼到MaxCompute執行。從輸出資訊的Logview URL中可以查看執行計畫圖和結果。

通過SDK使用指令碼模式

在Java/Python SDK中可以直接執行一個SQL指令碼,Java SDK詳情請參見Java SDK介紹,Python 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 {
		// 阿里雲帳號AccessKey擁有所有API的存取權限,風險很高。強烈建議您建立並使用RAM使用者進行API訪問或日常營運,請登入RAM控制台建立RAM使用者
		// 此處以把AccessKey 和 AccessKeySecret 儲存在環境變數為例說明。您也可以根據業務需要,儲存到設定檔裡
		// 強烈建議不要把 AccessKey 和 AccessKeySecret 儲存到代碼裡,會存在密鑰泄漏風險
    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
# 阿里雲帳號AccessKey擁有所有API的存取權限,風險很高。強烈建議您建立並使用RAM使用者進行API訪問或日常營運,請登入RAM控制台建立RAM使用者
# 此處以把AccessKey 和 AccessKeySecret 儲存在環境變數為例說明。您也可以根據業務需要,儲存到設定檔裡
# 強烈建議不要把 AccessKey 和 AccessKeySecret 儲存到代碼裡,會存在密鑰泄漏風險
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 中運行指令碼前,需完成以下準備:

  1. 安裝IntelliJ IDEA

  2. 管理專案串連

  3. 建立MaxCompute Script Module

編譯並運行指令碼後,MaxCompute Studio 顯示執行計畫。雖然指令碼包含多條語句,但執行計畫渲染為一個 DAG。