创建动态表(Dynamic Table)
动态表(Dynamic Table)就像一张“会自动更新的公式表”:你只需定义好 SQL 计算逻辑,每当源表有新数据,系统会自动增量更新结果。查询时直接读取已算好的结果,无需重跑全量,也无需手动配置调度任务。
选型参考 :
场景 推荐方案 需要定期刷新的聚合或转换结果,结果需要随时可查 Dynamic Table 需要捕获源表的行级变更(INSERT / UPDATE / DELETE) Table Stream 需要持续从 Kafka 或对象存储导入原始数据 Pipe 调度逻辑复杂、依赖多个上下游任务,或涉及非 SQL 处理 Studio 任务
功能概述
动态表是一种基于查询定义自动增量刷新的数据对象。创建时通过 SQL 查询定义数据加工逻辑,刷新时自动获取源表的增量数据,采用增量算法进行计算。
更多使用方式参考 动态表介绍 和 增量计算原理 。
创建语法
简化语法(推荐)
CREATE [ OR REPLACE | IF NOT EXISTS ] DYNAMIC TABLE [schema_name.]<dt_name>
[ (column_list) ]
[ PARTITIONED BY (column_name) ]
[ CLUSTERED BY (column_name) ]
[ COMMENT '<comment>' ]
[ PROPERTIES ('data_lifecycle'='<days>') ]
REFRESH INTERVAL <interval> VCLUSTER <vc_name>
AS <query>;
示例 :
-- 最简单的创建方式:每天刷新一次
CREATE DYNAMIC TABLE sales_daily
REFRESH INTERVAL 1 DAY VCLUSTER DEFAULT
AS SELECT DATE(created_at) AS dt, SUM(amount) AS total FROM orders GROUP BY 1;
-- 指定刷新间隔和计算集群:每小时刷新一次
CREATE DYNAMIC TABLE hourly_stats
REFRESH INTERVAL 1 HOUR VCLUSTER default_ap
AS SELECT DATE_TRUNC('hour', created_at) AS hour, COUNT(*) AS cnt FROM events GROUP BY 1;
完整语法
CREATE [ OR REPLACE | IF NOT EXISTS ] DYNAMIC TABLE dtname
[ (column_list) ]
[ PARTITIONED BY (column_name) ]
[ CLUSTERED BY (column_name) ]
[ COMMENT view_comment ]
[ PROPERTIES ('data_lifecycle'='day_num') ]
[ refreshOption ]
AS <query>;
refreshOption ::=
REFRESH
[ START WITH timestamp_expr ] [ interval_time ] VCLUSTER vcname
参数说明
必需参数
dtnamedtname
:动态表名称,支持 schema_name.table_nameschema_name.table_name
格式跨 schema 创建。
AS queryAS query
:定义动态表的计算逻辑,每次刷新都会基于此查询计算结果。
可选参数
IF NOT EXISTSIF NOT EXISTS
:跳过创建而不报错——当同名动态表已存在时使用,不能与 OR REPLACEOR REPLACE
同时指定。
OR REPLACEOR REPLACE
:原地更新表定义,保留已有数据和权限——用于修改列结构或 SQL 逻辑时,避免删表重建导致数据丢失。
OR REPLACEOR REPLACE
后下一次 REFRESH 是增量还是全量,取决于改动类型:
改动类型 下次刷新方式 仅增减透传列(不改计算逻辑) 增量 修改 WHERE 条件、JOIN key、GROUP BY key 全量 新增参与计算的列(如 j * 1j * 1
) 全量
-- 示例:修改 WHERE 条件后,下次 REFRESH 触发全量刷新
CREATE OR REPLACE DYNAMIC TABLE change_table (i, j) AS
SELECT * FROM dy_base_a WHERE i > 3;
REFRESH DYNAMIC TABLE change_table;
-- 结果:全量重算,只保留 i > 3 的行
column_listcolumn_list
:为输出列指定名称或注释——当 SELECT 中包含表达式(如 j + 1j + 1
)时必须指定,否则列名不可预测。列类型由 AS queryAS query
的 SELECT 结果自动推断,不能在此处指定类型;如需控制类型,在 SELECT 中用 CASTCAST
显式转换。
-- 方式一:在 column_list 中指定名称和注释
CREATE DYNAMIC TABLE change_table_dy (i, j_dd COMMENT 'test') AS
SELECT i, j + 1 FROM dy_base_a;
-- 方式二:在 SELECT 中使用别名
CREATE DYNAMIC TABLE change_table_dy AS
SELECT i, j + 1 AS j_add FROM dy_base_a;
PARTITIONED BY (col)PARTITIONED BY (col)
:按指定列对结果数据分区——适合数据量大、查询经常按某列过滤(如按日期、地区)的场景,可显著减少扫描范围。
CREATE DYNAMIC TABLE change_table_dy (i, j_dd COMMENT 'test') PARTITIONED BY (j_dd) AS
SELECT i, j + 1 FROM dy_base_a;
CLUSTERED BY (col)CLUSTERED BY (col)
:按指定列对结果数据散列分桶——适合数据量大、查询经常按某列做 JOIN 或聚合的场景,可减少数据倾斜。建议选取值范围大、重复键值少的列;一般按每桶 128MB–1GB 估算桶数,未指定时默认 256 个桶。
SORTED BY (col)SORTED BY (col)
:可选,指定桶内数据的排序方式,建议与 CLUSTERED BYCLUSTERED BY
保持一致以获得更好的查询性能。
-- 创建分桶动态表
CREATE DYNAMIC TABLE change_table_dy (i, j_dd COMMENT 'test')
CLUSTERED BY (j_dd) INTO 16 BUCKETS
AS SELECT i, j + 1 FROM dy_base_a;
-- 分桶并指定桶内排序
CREATE DYNAMIC TABLE change_table_dy (i, j_dd COMMENT 'test')
CLUSTERED BY (j_dd) SORTED BY (j_dd) INTO 16 BUCKETS
AS SELECT i, j + 1 FROM dy_base_a;
COMMENTCOMMENT
:为动态表添加注释说明。
refreshOptionrefreshOption
(刷新选项):控制数据新鲜度和计算成本的核心配置,由以下子参数组成:
START WITH timestamp_exprSTART WITH timestamp_expr
:指定首次刷新的时间点——不填则从创建时立即开始。支持时间戳字面量或表达式,如 '2023-11-07 14:49:18''2023-11-07 14:49:18'
、current_timestamp() - interval '12' hourscurrent_timestamp() - interval '12' hours
。时间点不能早于源表的 Time Travel 保留范围(默认 1 天),否则报错。
INTERVAL interval_timeINTERVAL interval_time
:控制刷新频率,直接决定数据新鲜度和计算资源消耗——间隔越短数据越及时,但消耗的计算资源越多。最小值为 1 分钟(INTERVAL '1' MINUTEINTERVAL '1' MINUTE
或 INTERVAL '60' SECONDINTERVAL '60' SECOND
)。只写 START WITHSTART WITH
不写 INTERVALINTERVAL
时,仅在指定时间点刷新一次。
语法 描述 示例 INTERVAL 'n' DAYINTERVAL 'n' DAY
指定天间隔 INTERVAL '1' DAYINTERVAL '1' DAY
表示 1 天INTERVAL 'n' HOURINTERVAL 'n' HOUR
指定小时隔 INTERVAL '23' HOURINTERVAL '23' HOUR
表示 23 小时INTERVAL 'n' MINUTEINTERVAL 'n' MINUTE
指定分钟隔 INTERVAL '59' MINUTEINTERVAL '59' MINUTE
表示 59 分钟INTERVAL 'n' SECONDINTERVAL 'n' SECOND
指定秒间隔 INTERVAL '59.999' SECONDINTERVAL '59.999' SECOND
表示 59.999 秒
VCLUSTER vc_nameVCLUSTER vc_name
:指定执行刷新任务的计算集群——自动刷新会持续消耗计算资源,必须明确指定,避免占用交互查询集群。未指定时默认使用当前会话的集群。
⚠️ 建议使用 GENERAL(通用型)集群。ANALYTICS(分析型)集群不支持刷新过程中的自动小文件合并,长期运行会产生大量小文件,影响查询性能。
调度方式
动态表有两种调度方式:
方式 适用场景 缺点 DDL 内置调度(REFRESH INTERVAL) 快速验证、开发测试 无告警、无依赖编排,只能手动查 SQL 确认状态 Studio Task 调度(推荐) 生产环境 需额外创建 Task
生产环境推荐使用 Studio Task 调度。DDL 内置调度(REFRESH INTERVAL)无法配置告警,刷新失败不会主动通知,只能手动执行
SHOW DYNAMIC TABLE REFRESH HISTORYSHOW DYNAMIC TABLE REFRESH HISTORY
检查状态。
DDL 内置调度
在 CREATE 语句中写 REFRESH INTERVAL 子句,Lakehouse 按频率自动触发:
CREATE DYNAMIC TABLE sales_daily
REFRESH INTERVAL 1 DAY VCLUSTER DEFAULT
AS SELECT DATE(created_at) AS dt, SUM(amount) AS total FROM orders GROUP BY 1;
Studio Task 调度
在 Studio 中创建调度任务,任务内容为 REFRESH 命令。
非分区 DT/动态分区 DT:
REFRESH DYNAMIC TABLE schema_name.dt_name;
静态分区 DT(带参数):
SET dt.args.ds = '${bizdate}';
REFRESH DYNAMIC TABLE schema_name.dt_name PARTITION (ds = '${bizdate}');
必须配置自依赖 :同一个 DT 禁止并发 REFRESH(会导致写冲突或数据不一致)。Task 必须开启自依赖,确保上一个实例完成后下一个才启动。静态分区声明的DT不同分区可以并行刷新;同一分区,非分区,动态分区 DT 禁止并发刷新。
上游依赖 :如果 DT 的源表数据需要等待上游任务产出后再刷新,配置上游依赖;如果源表是实时写入表则不需要。
告警配置 :生产环境建议配置失败告警、超时告警、未运行告警。
多级 DT 管道的调度编排
多个 DT 形成上下游依赖时(如 DT_A → DT_B → DT_C),每个 DT 对应一个 Studio Task,通过任务依赖关系保证执行顺序:
Task_A(REFRESH DT_A)
└─ Task_B(REFRESH DT_B,依赖 Task_A)
└─ Task_C(REFRESH DT_C,依赖 Task_B)
注意事项
调度方式 :生产环境优先选择 Studio Task 调度 REFRESH,便于与上下游任务编排依赖、统一监控告警。详见 DT 调度部署规范 。
Time Travel 依赖 :动态表的增量刷新是基于基表的历史版本。历史版本取决于 TIME TRAVEL (data_retention_daysdata_retention_days
) 参数。如果指定的版本不存在则会报错。Lakehouse 默认保留数据一天。
参数化动态表
Dynamic Table 支持参数化定义,用于将传统的离线调度任务(如
select * from source_table where pt=${bizdate}select * from source_table where pt=${bizdate}
)转换为增量任务。
参数化定义由两部分组成:
定义参数 :在 SQL 中使用 SESSION_CONFIGS()['dt.args.xx']SESSION_CONFIGS()['dt.args.xx']
代替硬编码的分区值。
传入参数 :在刷新时通过 SET dt.args.xx = value;SET dt.args.xx = value;
传入具体值。
示例
-- 1. 创建源表
CREATE TABLE source_table (col1 string, col2 string, pt string) PARTITIONED BY (pt);
-- 2. 定义动态表(使用参数过滤)
CREATE DYNAMIC TABLE incremental_dt (col1, col2, pt) PARTITIONED BY (pt) AS
SELECT col1, nvl(col2, col1), pt
FROM source_table
WHERE pt = SESSION_CONFIGS()['dt.args.pt'];
-- 3. 刷新时传入参数
SET dt.args.pt = '2024-11-13';
REFRESH DYNAMIC TABLE incremental_dt PARTITION (pt = '2024-11-13');
刷新行为
触发场景 刷新方式 说明 首次创建后第一次 REFRESH 全量 没有历史基准,必须扫描全量数据建立初始状态 首次刷新某个分区(分区表) 全量 该分区尚无历史基准 参数化 DT:参数值与上次相同 增量 SQL 过滤条件未变,只处理增量数据 参数化 DT:参数值发生变化 全量 参数变化等同于修改了表的计算定义 OR REPLACEOR REPLACE
修改了透传列(增减列)增量 列变化不影响其他列的计算逻辑 OR REPLACEOR REPLACE
修改了 JOIN key / GROUP key 或加工逻辑全量 历史数据需要按新逻辑重算 直接通过 DML 写入动态表后再 REFRESH 全量 数据状态已被外部修改,增量基准不可信
多级分区刷新
语句刷新分区表时,必须按照表的分区层级顺序指定
partition_specpartition_spec
。不能跳过中间层级。
合法指定 :指定高层级和部分低层级分区。
set dt.args.day='2024-11-13';
set dt.args.hour='23';
REFRESH DYNAMIC TABLE dt PARTITION (day='2024-11-13', hour='23');
非法指定 :跳过中间层级(如跳过 hour 直接指定 min)。
-- 错误示例:跳过了 hour
set dt.args.day='2024-11-13';
set dt.args.min='30';
REFRESH DYNAMIC TABLE dt PARTITION (day='2024-11-13', min='30');
分区策略选择
动态表有两种创建语法:静态分区 DT 和动态分区 DT (非分区 DT 可视为动态分区的特例)。两者在创建语法、刷新行为和增量行为上有本质区别。
核心概念
静态分区 DT (带
SESSION_CONFIGSSESSION_CONFIGS
参数的分区 DT):SQL 通过
SESSION_CONFIGS()SESSION_CONFIGS()
引用分区参数,每次 REFRESH 时指定具体分区值。每个分区独立刷新,可视为独立的 DT 单元。
动态分区 DT (非分区 DT / 不带参数的 DT):SQL 不引用
SESSION_CONFIGS()SESSION_CONFIGS()
,或虽然分区但分区值由查询逻辑动态产生。每次 REFRESH 处理所有源表的全部增量数据。
动态分区 DT 不允许除 REFRESH 以外的任何命令修改数据(INSERT / UPDATE / DELETE / MERGE 均不可用),数据完全由 REFRESH 驱动。因此,以下 ETL 场景不适合 动态分区 DT:
需要手动补数(如发现几行数据有误,需要直接 UPDATE)
需要按条件删除数据(如清理脏数据、删除过期记录)
需要 MERGE INTO 做 upsert(如 CDC 场景消费 Stream 后合并到目标表)
需要 INSERT INTO 追加外部数据(如手动导入一批补充数据)
需要独立回填或重刷某个分区(动态分区 DT 只能全表刷新,不能单独刷某个分区)
关键差异对比
维度 静态分区 DT 动态分区 DT SQL 是否含 SESSION_CONFIGS()? 是,用于引用分区参数 否 REFRESH 语法 REFRESH ... PARTITION(ds='xxx') REFRESH ...(不带 PARTITION) 增量范围 只处理指定分区的增量数据 处理所有源表的全部增量数据 调度方式 外部调度器每次触发一个分区 由时间或外部调度器触发 数据生命周期 按分区管理,可独立回填/删除 整表统一管理 状态表 每个分区独立维护 全局统一维护 适合的数据模式 T+1 批处理、按时间分区的 ETL 实时流、全局聚合、无明确分区键
选择决策树
数据是否有明确的时间/业务分区键?
│
├─ 有 → 原始 ETL 是否按分区做 INSERT OVERWRITE?
│ │
│ ├─ 是 → 使用静态分区 DT
│ │ (保持原有分区粒度,每个分区独立刷新)
│ │
│ └─ 否 → 数据量是否大?是否需要按分区管理生命周期?
│ │
│ ├─ 是 → 使用静态分区 DT
│ │ (即使原来没有分区,也建议加分区以便管理)
│ │
│ └─ 否 → 使用动态分区 DT
│ (场景简单,无需分区管理)
│
└─ 无 → 使用动态分区 DT
(全局聚合、实时汇总等)
分区粒度选择
选择静态分区 DT 后,还需要确定分区粒度:
数据模式 推荐粒度 说明 大致有序、少量迟到数据 小时级(dt_hour) 兼顾粒度与管理复杂度 T+1 批量导入 天级(ds) 最常见的 ETL 场景 按业务周期 周/月 报表场景 多级分区 天 + 小时(ds, hour) 需要更细粒度的生命周期管理
选择原则:
粒度越细 → 每次刷新数据量越小 → 增量效率越高
粒度越细 → 分区数越多 → 管理和调度越复杂
粒度应与数据写入频率匹配:如果数据按小时写入,分区粒度不应细于小时
从原始 ETL 推断分区策略
原始 ETL 模式 推荐 DT 分区策略 INSERT OVERWRITE TABLE t PARTITION(ds='${ds}') 静态分区 DT,天级 INSERT OVERWRITE TABLE t PARTITION(ds='${ds}', hour='${hour}') 静态分区 DT,天+小时级 INSERT OVERWRITE TABLE t PARTITION(ds)(动态分区写入) 动态分区 DT 或静态分区 DT(取决于是否需要按分区管理) INSERT INTO TABLE t SELECT ...(无分区) 动态分区 DT INSERT OVERWRITE TABLE t SELECT ...(全表覆盖) 动态分区 DT
场景案例
案例一:将离线任务转换为增量任务
原始离线 SQL :
INSERT OVERWRITE TABLE dim.dim_shop_sales_channel_misc PARTITION (pt = '${bizdate}')
SELECT ... FROM ... WHERE pt = '${bizdate}';
转换步骤 :
将 ${bizdate}${bizdate}
替换为 SESSION_CONFIGS()['dt.args.bizdate']SESSION_CONFIGS()['dt.args.bizdate']
。
创建 Dynamic Table。
调度刷新命令。
-- 1. 创建参数化动态表
CREATE DYNAMIC TABLE dim.dim_shop_sales_channel_misc PARTITIONED BY (pt) AS
SELECT channel_code, channel_name, channel_type, channel_uid, id AS fxiaoke_id, account_no AS fxiaoke_account_no, pt
FROM ...
WHERE pt = SESSION_CONFIGS()['dt.args.bizdate'];
-- 2. 调度刷新命令(由 Studio 替换具体日期)
SET dt.args.bizdate = '20241130';
REFRESH DYNAMIC TABLE dim.dim_shop_sales_channel_misc PARTITION (pt = '20241130');
案例二:多表 Join 分析
-- 创建源表
CREATE TABLE doc_dt_users (user_id BIGINT, user_name STRING, city STRING);
CREATE TABLE doc_dt_orders (order_id BIGINT, user_id BIGINT, amount DECIMAL(12,2));
INSERT INTO doc_dt_users VALUES (1, 'Alice', '上海'), (2, 'Bob', '北京'), (3, 'Carol', '广州');
INSERT INTO doc_dt_orders VALUES (101, 1, 299.00), (102, 1, 150.50), (103, 2, 88.00), (104, 3, 520.00), (105, 3, 200.00);
-- 创建动态表(column_list 只写列名,不带类型)
CREATE DYNAMIC TABLE doc_dt_user_purchase_analysis (
user_id,
user_name,
city,
total_orders,
total_amount
)
COMMENT '用户购买行为实时分析表'
REFRESH INTERVAL 1 HOUR VCLUSTER DEFAULT
AS
SELECT
u.user_id, u.user_name, u.city,
COUNT(o.order_id) AS total_orders,
SUM(o.amount) AS total_amount
FROM doc_dt_users u
LEFT JOIN doc_dt_orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.user_name, u.city;
-- 手动触发刷新
REFRESH DYNAMIC TABLE doc_dt_user_purchase_analysis;
-- 查询结果
SELECT * FROM doc_dt_user_purchase_analysis ORDER BY user_id;
+---------+-----------+------+--------------+--------------+
| user_id | user_name | city | total_orders | total_amount |
+---------+-----------+------+--------------+--------------+
| 1 | Alice | 上海 | 2 | 449.50 |
| 2 | Bob | 北京 | 1 | 88.00 |
| 3 | Carol | 广州 | 2 | 720.00 |
+---------+-----------+------+--------------+--------------+
参考文档
相关指南
实时数据管道选型指南 :Pipe、Table Stream、Dynamic Table 的选型对比
动态表开发入门 :动态表的增量刷新机制、调度配置、与 Task 的对比选型