Flink SQL CREATE 语句从建表到 CTAS/RTAS,一次讲清

1. CREATE 语句能干嘛

CREATE 系列语句的目标只有一个:把对象注册到当前或指定 Catalog 中,让它们能被后续 SQL 直接引用(查询、写入、函数调用、模型推理等)。

当前 Flink SQL 常见支持:

  1. CREATE TABLE
  2. [CREATE OR] REPLACE TABLE
  3. CREATE CATALOG
  4. CREATE DATABASE
  5. CREATE VIEW
  6. CREATE FUNCTION
  7. CREATE MODEL

2. 如何执行 CREATE:Java TableEnvironment 一把梭

在 Java 里,CREATE 语句就是一条 SQL 字符串,直接丢给 TableEnvironment.executeSql()

java 复制代码
TableEnvironment tableEnv = TableEnvironment.create(...);

// 1) 注册源表
tableEnv.executeSql(
  "CREATE TABLE Orders (`user` BIGINT, product STRING, amount INT) WITH (...)"
);

// 2) 注册 sink 表
tableEnv.executeSql(
  "CREATE TABLE RubberOrders(product STRING, amount INT) WITH (...)"
);

// 3) 查询
Table result = tableEnv.sqlQuery(
  "SELECT product, amount FROM Orders WHERE product LIKE '%Rubber%'"
);

// 4) 写入
tableEnv.executeSql(
  "INSERT INTO RubberOrders " +
  "SELECT product, amount FROM Orders WHERE product LIKE '%Rubber%'"
);

SQL Client 场景通常更简单:直接在 CLI 里执行 CREATE 即可(依赖通常已带齐)。

3.1 总体语法骨架(你可以把它当模板)

你在生产里 90% 的建表,都可以从这个骨架改出来:

sql 复制代码
CREATE TABLE [IF NOT EXISTS] [catalog.][db.]table_name (
  -- 1) 物理列 / 元数据列 / 计算列(三选一或混合)
  ...
  -- 2) watermark(可选,但事件时间流基本必备)
  [ WATERMARK FOR rowtime AS ... ]
  -- 3) 主键/约束(可选,但 upsert/更新流强烈推荐)
  [ PRIMARY KEY (...) NOT ENFORCED ]
)
[COMMENT '...']
[ DISTRIBUTED BY ... | DISTRIBUTED INTO ... ]   -- 桶/分布(可选)
[ PARTITIONED BY (...) ]                        -- 分区(可选,filesystem 常用)
WITH (
  'connector'='...',
  ...
)
[ LIKE source_table (...) | AS select_query ];

4. 列的三种形态:物理列 / 元数据列 / 计算列

4.1 物理列(Physical Columns)

最普通的列:定义字段名、类型、顺序。connector/format 读取与写出基本都依赖物理列

sql 复制代码
CREATE TABLE MyTable (
  user_id BIGINT,
  name STRING
) WITH (...);

4.2 元数据列(Metadata Columns)

用于把 connector/format 的"行级元信息"显式暴露出来(比如 Kafka 的 timestamp、offset 等)。

sql 复制代码
CREATE TABLE MyTable (
  user_id BIGINT,
  name STRING,
  record_time TIMESTAMP_LTZ(3) METADATA FROM 'timestamp'
) WITH (
  'connector'='kafka',
  ...
);

常见要点:

  1. FROM 'xxx' 可以省略:列名就当 metadata key
  2. 类型允许兼容 cast(比如把 timestamp 映射成 BIGINT)
  3. VIRTUAL:只参与读(source-to-query),不参与写出(query-to-sink)
sql 复制代码
CREATE TABLE MyTable (
  `timestamp` BIGINT METADATA,
  `offset` BIGINT METADATA VIRTUAL,
  user_id BIGINT,
  name STRING
) WITH ('connector'='kafka', ...);

4.3 计算列(Computed Columns)

用表达式生成的"虚拟列",不会物理落盘/落 Kafka。非常常用来:

  • 补齐处理时间:proctime AS PROCTIME()
  • 从字符串/JSON 转换事件时间
  • 做派生指标:cost AS price * quantity
sql 复制代码
CREATE TABLE MyTable (
  user_id BIGINT,
  price DOUBLE,
  quantity DOUBLE,
  cost AS price * quantity
) WITH (...);

计算列不能作为 INSERT INTO 的目标列(因为不持久化)。

5. WATERMARK:事件时间语义的"开关"

水位线声明形式:

sql 复制代码
WATERMARK FOR rowtime_column AS watermark_expression

关键规则(记住这三条,基本不踩坑):

  1. rowtime_column 必须是表中已存在的列
  2. 事件时间列通常要求是 TIMESTAMP(3)(可来自计算列)
  3. watermark 表达式返回值也必须是 TIMESTAMP(3)

常用策略:

5.1 严格递增(几乎不允许乱序)

sql 复制代码
WATERMARK FOR rowtime AS rowtime

5.2 允许 5 秒乱序(生产最常见)

sql 复制代码
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND

完整例子:

sql 复制代码
CREATE TABLE Orders (
  `user` BIGINT,
  product STRING,
  order_time TIMESTAMP(3),
  WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (...);

6. PRIMARY KEY:不是约束检查,而是"优化提示"

Flink 的主键语义要记住一句话:

Flink 只支持 NOT ENFORCED,不会帮你检查数据是否真的唯一;你要自己保证。

但主键仍然非常重要,因为它会影响:

  • Planner 的优化(例如 upsert、changelog 推导)
  • 下游 sink 的写入语义(比如 upsert-kafka/jdbc upsert)

写法两种:

6.1 列级主键

sql 复制代码
id BIGINT PRIMARY KEY NOT ENFORCED

6.2 表级主键

sql 复制代码
PRIMARY KEY (id, biz_date) NOT ENFORCED

注意:主键列不可为空;Flink 会据此调整列的 nullability 假设。

7. PARTITIONED BY 与 DISTRIBUTED:分区 vs 桶(别搞混)

7.1 PARTITIONED BY:典型用于 filesystem sink

按分区列生成目录层级(Hive/Filesystem 场景最常见):

sql 复制代码
PARTITIONED BY (dt, region)

7.2 DISTRIBUTED / BUCKET:典型用于负载均衡与并行写

桶(buckets)把"可能无限的 keyspace"切成更可管理的小片,利于并行处理与下游存储的写放大控制。

常见写法:

sql 复制代码
-- 指定 HASH + 桶数
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED BY HASH(uid) INTO 4 BUCKETS;

-- 让 connector 自选算法,只给桶键与桶数
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED BY (uid) INTO 4 BUCKETS;

-- 只给桶键,桶数交给 connector
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED BY (uid);

-- 只给桶数
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED INTO 4 BUCKETS;

8. WITH Options:连接器与格式的"入口"

WITH ('k1'='v1', 'k2'='v2') 用来描述 connector/format 等属性。要点:

  1. key/value 都必须是字符串字面量
  2. 表一旦注册,既可以作为 source 也可以作为 sink(直到被 DML 引用时才确定角色)
  3. 表名可以是:catalog.db.table / db.table / table

9. LIKE:复用表定义,做"继承 + 覆盖"

LIKE 可以基于已有表复用定义,并选择:

  • INCLUDING / EXCLUDING 哪些部分(约束、分布、分区、watermark、options、generated、metadata...)
  • 是否 OVERWRITING(遇到冲突 key 时用新表覆盖)

典型场景:复用 schema,把 connector 从 filesystem 换成 kafka,同时补 watermark

sql 复制代码
CREATE TABLE Orders (
  `user` BIGINT,
  product STRING,
  order_time TIMESTAMP(3)
) WITH (
  'connector'='kafka',
  'scan.startup.mode'='earliest-offset'
);

CREATE TABLE Orders_with_watermark (
  WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
  'scan.startup.mode'='latest-offset'
)
LIKE Orders;

10. CTAS:Create Table As Select(建表 + 插入一条命令完成)

CTAS 适合"快速从查询结果生成一张新表":

sql 复制代码
CREATE TABLE my_ctas_table
WITH ('connector'='kafka', ...)
AS
SELECT id, name, age
FROM source_table
WHERE MOD(id, 10) = 0;

你也可以在 CREATE 部分显式加:

  • computed columns
  • watermark
  • primary key
  • distributed

但注意两类限制(很容易踩):

  1. 暂不支持创建 temporary table
  2. 暂不支持创建 partitioned table
  3. 默认 非原子:插入失败不会自动回滚删除已创建的表

10.1 想要"原子 CTAS"怎么办?

两件事同时满足:

  1. sink 侧实现了 CTAS 原子语义(通常依赖 staging 能力)
  2. 设置:
sql 复制代码
SET 'table.rtas-ctas.atomicity-enabled' = 'true';

11. RTAS / REPLACE TABLE:一条语句"替换并回填"

REPLACE TABLE AS SELECT / CREATE OR REPLACE TABLE AS SELECT 用于:

  • 表存在:直接替换(语义上像 drop + create + insert)
  • CREATE OR REPLACE:不存在就创建,存在就替换
sql 复制代码
REPLACE TABLE my_rtas_table
WITH ('connector'='kafka', ...)
AS
SELECT id, name, age
FROM source_table
WHERE MOD(id, 10) = 0;

RTAS 同样默认 非原子,并且语义上先 drop 再建再写;如果在 in-memory catalog 下,drop 只是从 catalog 移除,不一定删除物理数据(这点生产上要特别注意)。

想要原子 RTAS:同 CTAS,依赖 sink 支持 + table.rtas-ctas.atomicity-enabled=true

12. 其它 CREATE:Catalog / Database / View / Function / Model

12.1 CREATE CATALOG

sql 复制代码
CREATE CATALOG [IF NOT EXISTS] my_catalog
WITH ('type'='...', ...);

12.2 CREATE DATABASE

sql 复制代码
CREATE DATABASE [IF NOT EXISTS] my_db
WITH ('k'='v', ...);

12.3 CREATE VIEW(支持 TEMPORARY)

sql 复制代码
CREATE [TEMPORARY] VIEW [IF NOT EXISTS] my_view AS
SELECT ...

12.4 CREATE FUNCTION(JAVA/SCALA/PYTHON)

支持临时函数与系统临时函数:

sql 复制代码
CREATE TEMPORARY FUNCTION my_udf
AS 'com.xxx.MyUdf'
LANGUAGE JAVA;

USING JAR 当前只支持 JAVA/SCALA(把实现与依赖 jar 引入)。

12.5 CREATE MODEL(把"模型推理"注册成对象)

sql 复制代码
CREATE MODEL sentiment_analysis_model
INPUT (text STRING)
OUTPUT (sentiment STRING)
WITH (
  'provider' = 'openai',
  'endpoint' = 'https://api.openai.com/v1/chat/completions',
  'api-key' = '<YOUR KEY>',
  'model' = 'gpt-3.5-turbo',
  'system-prompt' = '...'
);

13. 最佳实践速查(建议你写进博客结尾)

  1. Kafka/Upsert/JDBC 这类更新流:优先声明 PRIMARY KEY ... NOT ENFORCED
  2. 事件时间作业:一定写 WATERMARK(并用合理乱序区间)
  3. 元数据列:offset 这类"只读"字段用 VIRTUAL,避免写出 schema 不一致
  4. 用 LIKE 做"复用定义 + 覆盖 options",比复制粘贴更稳
  5. CTAS/RTAS 上生产前先确认:是否需要原子性?sink 是否支持 staging?
相关推荐
大大大大晴天4 小时前
Flink生产问题排障-HBase NotServingRegionException
flink·hbase
大大大大晴天1 天前
Flink生产问题排障-Kryo serializer scala extensions are not available
大数据·flink
tryCbest5 天前
数据库SQL学习
数据库·sql
cowboy2585 天前
mysql5.7及以下版本查询所有后代值(包括本身)
数据库·sql
努力的lpp5 天前
SQL 报错注入
数据库·sql·web安全·网络安全·sql注入
麦聪聊数据5 天前
统一 Web SQL 平台如何收编企业内部的“野生数据看板”?
数据库·sql·低代码·微服务·架构
山峰哥5 天前
吃透 SQL 优化:告别慢查询,解锁数据库高性能
服务器·数据库·sql·oracle·性能优化·编辑器
yumgpkpm5 天前
AI视频生成:Wan 2.2(阿里通义万相)在华为昇腾下的部署?
人工智能·hadoop·elasticsearch·zookeeper·flink·kafka·cloudera
轩情吖5 天前
MySQL初识
android·数据库·sql·mysql·adb·存储引擎
james的分享5 天前
大数据领域核心 SQL 优化框架Apache Calcite介绍
大数据·sql·apache·calcite