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?
相关推荐
2501_916766544 小时前
JDBC以及工具类介绍
sql
她说..5 小时前
Spring AOP场景4——事务管理(源码分析)
java·数据库·spring boot·后端·sql·spring·springboot
男孩李6 小时前
linux下执行pg数据的sql文件,报错error:permission denied for schema plat
数据库·sql
小阿宁的猫猫6 小时前
sqlmap的使用
sql·网络安全·php
嘟嘟w7 小时前
SQL注入是什么
数据库·sql·oracle
surtr17 小时前
数据库基础(数据库原理和应用)
数据库·sql·mysql·oracle·database
卓码软件测评8 小时前
CMA/CNAS软件测评机构:【Gatling数据库性能关联测试JDBC连接和SQL执行时间监控】
数据库·sql·测试工具·性能优化·测试用例
hid646637228 小时前
基于改进粒子群算法的无人机三维路径规划——MATLAB运行效果图
sql
guoyiguang29 小时前
mysql in 查询 没有限制1000个,默认是sql大小4M大小
数据库·sql·mysql