脚本模式(Script Mode SQL)将多条 SQL 语句作为一个整体编译和提交,生成一份执行计划,只需排队一次、执行一次。适用于 ETL 数据管道、周期性批处理,以及需要多条语句协同完成的查询编排场景。脚本模式支持普通模式(默认,原子性执行)和单步执行模式(逐条串行执行)。
适用场景
-
复杂单语句改写:将深度嵌套的子查询拆分为一系列可读的表变量赋值语句。
-
多语句管道:将逻辑上相关的多条语句合并为一个作业统一提交,减少排队和调度开销。普通模式下所有语句原子性执行;单步执行模式下语句逐条串行执行,适用于需要先写后读或跨平台迁移的场景。详情请参见执行模式。
语法结构
-
语句类型:脚本模式支持SET语句、部分DDL语句(不支持结果为屏显类型的语句如DESC、SHOW)、DML语句。
-
语句顺序:脚本的完整形式是
SET→DDL→DML的固定顺序,每个语句类型都可以包含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 部分添加 |
|
编译 |
所有 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语句必须放在脚本末尾。
创建普通表
脚本模式支持创建普通表。语法详情请参见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
使用说明
-
当分支只包含一条语句时,关键字
BEGIN和END可省略(类似 Java 中的{ })。 -
IF 分支内不支持
CREATE TABLE、ALTER TABLE、TRUNCATE 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 查看执行计划和结果。

在此节点中进行脚本模式编辑,编辑完成后单击工具栏的运行图标,提交脚本到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 中运行脚本前,需完成以下准备:
编译并运行脚本后,MaxCompute Studio 显示执行计划。虽然脚本包含多条语句,但执行计划渲染为一个 DAG。