Flink SQL 查询(Queries)从 sqlQuery 到 executeSql

1. 查询的核心入口:TableEnvironment.sqlQuery()

在 Flink 里,你不会直接用 Statement stmt = ... 这种 JDBC 风格,而是通过 TableEnvironment 来执行 SQL。

对于 SELECT / VALUES 这类"有结果集"的查询,入口是:

java 复制代码
Table result = tableEnv.sqlQuery("SELECT ...");

这里有几个关键点:

1.1 查询的输入:必须是「可见的表」

SQL 语句里的表名,必须先在 TableEnvironment 中"可见",才可以被引用。常见几种方式:

  • 通过 CREATE TABLE DDL 创建表(典型:Kafka、文件、JDBC)
  • 通过 createTemporaryView 把 DataStream / Table 注册成视图
  • 通过 Catalog 管理多数据源
  • 直接从 Table API 创建 Table(然后用 toString() 内联)

比如从 DataStream 创建一个临时视图 Orders

java 复制代码
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

// 假设外部数据源产生三列 (user, product, amount)
DataStream<Tuple3<Long, String, Integer>> ds = env.addSource(...);

// 注册成临时视图 "Orders"
tableEnv.createTemporaryView(
    "Orders",
    ds,
    $("user"), $("product"), $("amount")
);

// 现在 SQL 就可以直接用 Orders 了
Table result = tableEnv.sqlQuery(
    "SELECT product, amount FROM Orders WHERE product LIKE '%Rubber%'"
);

1.2 内联 Table:Table.toString() 小技巧

有时候你已经拿到一个 Table,不想再注册个名字,Flink 提供了一个"小彩蛋":

  • Table.toString()自动给表起一个唯一的名字并注册到当前 TableEnvironment,
  • 然后返回这个名字。

所以你可以把 Table 直接拼进 SQL 里用,比如:

java 复制代码
Table table = tableEnv.fromDataStream(ds, $("user"), $("product"), $("amount"));

Table result = tableEnv.sqlQuery(
    "SELECT SUM(amount) FROM " + table + " WHERE product LIKE '%Rubber%'"
);

这段代码里:

  • table 被隐式注册成一个匿名表;
  • SQL 里直接引用这个匿名表名即可。

这种方式在你做复杂逻辑、Table API 和 SQL 混用时非常方便。

2. 查询如何真正执行:executeSql()Table.execute()

刚才说的 sqlQuery() 只构建查询计划,不会真正执行任务 ,它返回的是一个 Table

要让查询真的"跑起来",需要用到以下两个入口:

  • TableEnvironment.executeSql(sql)
  • Table.execute()

2.1 执行 SELECT / VALUES:返回 TableResult

对于 SELECT / VALUES 语句,你可以直接:

java 复制代码
TableResult tableResult = tableEnv.executeSql("SELECT * FROM Orders");

或者把 SQL 写成 sqlQuery 再执行:

java 复制代码
Table table = tableEnv.sqlQuery("SELECT * FROM Orders");
TableResult tableResult = table.execute();  // 等价于上面

TableResult 提供两种典型用法:

collect():迭代拿结果(需要手动关闭)
java 复制代码
try (CloseableIterator<Row> it = tableResult.collect()) {
    while (it.hasNext()) {
        Row row = it.next();
        // 处理每一行
    }
}  // try-with-resources 会自动调用 it.close()

注意:

  • 这是一个流式迭代器,流任务下它可以一直输出;
  • 不主动关闭迭代器,对长时间运行的作业可能会资源泄露
  • 一旦调用了 collect(),结果只能消费一次,不能再调用 print()
print():直接打印到控制台(调试神器)
java 复制代码
TableResult result = tableEnv.sqlQuery("SELECT * FROM Orders").execute();
result.print();

Flink 会自动把结果以表格形式输出,非常适合开发调试。

⚠️ 注意:collect()print() 都是单次消费 ,不能对同一个 TableResult 都调用一次,否则会报错。

2.2 不同 checkpoint 策略下的语义差异

Flink 是流批一体的,TableResult.collect()/print() 在不同模式下语义略有差异:

批任务 或 无 checkpoint 的流任务
  • 结果一生产出来就可以被客户端看到;
  • 不保证 exactly-once 或 at-least-once
  • 如果作业失败重启,客户端这边会抛异常。
exactly-once checkpoint 的流任务
  • 提供 端到端 exactly-once 交付保证;
  • 某条结果要等它所在的 checkpoint 完成后,才会暴露给客户端;
  • 通俗点:输出稍有延迟,但不会重复、不会丢
at-least-once checkpoint 的流任务
  • 提供 端到端至少一次 语义;
  • 结果会尽快输出,但在故障重启时可能重复

一般实战建议:

  • 日志调试、临时查询:可以不开 checkpoint,低延迟看结果就行;
  • 对结果一致性有要求:推荐开启 exactly-once + 外部支持事务的 Sink

3. 执行 INSERT:把结果写到外部系统

对于:

sql 复制代码
INSERT INTO RubberOrders
SELECT product, amount
FROM Orders
WHERE product LIKE '%Rubber%';

这类 DML,使用方式依然是 executeSql

java 复制代码
tableEnv.executeSql(
    "INSERT INTO RubberOrders " +
    "SELECT product, amount FROM Orders WHERE product LIKE '%Rubber%'"
);

这里的 RubberOrders 是一个预先通过 DDL 定义好的表,比如:

java 复制代码
final Schema schema = Schema.newBuilder()
    .column("product", DataTypes.STRING())
    .column("amount", DataTypes.INT())
    .build();

final TableDescriptor sinkDescriptor = TableDescriptor.forConnector("filesystem")
    .schema(schema)
    .option("path", "/path/to/file")
    .format(FormatDescriptor.forFormat("csv")
        .option("field-delimiter", ",")
        .build())
    .build();

tableEnv.createTemporaryTable("RubberOrders", sinkDescriptor);

之后所有 INSERT INTO RubberOrders ... 都会持续写入这个 Sink 表(Kafka / 文件 / JDBC 等)。

小结:

  • SELECT / VALUESexecuteSql(...)table.execute()TableResultcollect()/print()
  • INSERT / UPDATE / DELETEexecuteSql(...) 即可,通常不用再 collect 结果,而是看作业和 Sink 是否正常即可。

4. SQL 语法 & 字面量规则(和 Java 很像)

Flink SQL 基于 Apache Calcite,整体遵循 ANSI SQL 标准,但有几条特别容易踩坑的规则值得记一下。

4.1 标识符(表名、字段名、函数名)大小写规则

规则类似 Java:

  1. 无论是否加引号,原始大小写会被保留
  2. 匹配时是大小写敏感的

通常我们会全部用小写,避免和 Calcite 的各种大小写处理搞混。

如果字段名中有特殊字符(例如空格、关键字),可以用反引号包起来:

sql 复制代码
SELECT a AS `my field` FROM t;
SELECT `value`, `count` FROM my_table;

很多 SQL 实现是用双引号 ",Flink 更推荐 反引号 ,尤其当你字段名里有关键字时(比如 valuecount 等)。

4.2 字符串字面量 & 转义

  • 字符串统一用 单引号 '
sql 复制代码
SELECT 'Hello World';
  • 单引号内部如果再需要单引号,用 两个单引号 转义:
sql 复制代码
SELECT 'It''s me';
-- 输出:It's me

你在 SQL Client 里就能看到类似:

text 复制代码
Flink SQL> SELECT 'Hello World', 'It''s me';
+-------------+---------+
|      EXPR$0 |  EXPR$1 |
+-------------+---------+
| Hello World | It's me |
+-------------+---------+

如果你需要精确写 Unicode 字符,有两种方式:

① 使用 U&'' 语法
sql 复制代码
-- 默认用反斜杠作为转义符
SELECT U&'\263A';                 -- ☺
SELECT U&'#263A' UESCAPE '#';     -- 自定义转义符为 #

以下是常见的转义序列:

写法 含义
\b backspace
\f form feed
\n newline(换行)
\r carriage return
\t tab
\o...\ooo 8 进制字节
\xh...\xhh 16 进制字节
\uxxxx / \Uxxxxxxxx 16/32 位 Unicode

示例:

sql 复制代码
SELECT e'\u0061\x61\141' AS c;
-- 或
SELECT E'\u0061\x61\141' AS c;

这三段都代表字符 a,最后输出 aaa

5. 一个完整小示例:从 inlined Table 到文件 Sink

把上文的所有概念串起来,我们做一个完整例子:

  • 从 DataStream 读取 user, product, amount
  • sqlQuery() 过滤 product 包含 "Rubber" 的记录;
  • 把结果写到文件 Sink。
java 复制代码
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.*;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

import static org.apache.flink.table.api.Expressions.$;

public class FlinkSqlQueriesDemo {
    public static void main(String[] args) throws Exception {
        // 1. 环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 2. DataStream -> 内联 Table
        DataStream<Tuple3<Long, String, Integer>> ds = env.addSource(...); // 自己实现 Source

        Table ordersTable = tableEnv.fromDataStream(
            ds,
            $("user"),
            $("product"),
            $("amount")
        );

        // 3. 用 sqlQuery 在内联表上做查询
        Table rubberOrders = tableEnv.sqlQuery(
            "SELECT product, amount FROM " + ordersTable +
            " WHERE product LIKE '%Rubber%'"
        );

        // 4. 定义文件 Sink 表
        Schema schema = Schema.newBuilder()
            .column("product", DataTypes.STRING())
            .column("amount", DataTypes.INT())
            .build();

        TableDescriptor sinkDescriptor = TableDescriptor.forConnector("filesystem")
            .schema(schema)
            .option("path", "/tmp/rubber_orders.csv")
            .format("csv")
            .build();

        tableEnv.createTemporaryTable("RubberOrders", sinkDescriptor);

        // 5. 把查询结果 INSERT INTO Sink
        rubberOrders.executeInsert("RubberOrders");

        env.execute("Flink SQL Queries Demo");
    }
}

这里用的是 Table.executeInsert("RubberOrders")(不同版本 API 名字略有差异,本质等价于做一条 INSERT INTO)。

6. 总结 & 实战建议

最后用几条要点再捋一遍:

  1. 构建查询:

    • sqlQuery() 只返回 Table,不触发执行;
    • SQL 里的表必须先在 TableEnvironment 中"可见"(DDL / View / Catalog / 内联 Table)。
  2. 执行查询:

    • executeSql("SELECT ...")table.execute()TableResult
    • collect() 流式迭代结果,记得关闭迭代器;
    • print() 快速在控制台看结果(只能用一次)。
  3. 写入外部系统:

    • INSERT INTO sink SELECT ...
    • Sink 表通过 DDL / TableDescriptor 预先定义好即可。
  4. 流批语义:

    • 批任务 / 无 checkpoint:快速看到结果,但不保证一致性;
    • exactly-once:结果稍延迟,保证端到端精确一次;
    • at-least-once:可能有重复,需要下游幂等处理。
  5. 语法注意事项:

    • 标识符大小写敏感,关键字要用反引号包起来;
    • 字符串用单引号,内部单引号要写成两个;
    • Unicode 和 C 风格转义可以帮你精确控制字符。
相关推荐
Han.miracle1 小时前
数据库圣经--Alter & 视图
数据库·sql·视图
路边草随风1 小时前
java 实现 flink 读 kafka 写 iceberg
java·flink·kafka
路边草随风1 小时前
java 实现 flink cdc 读 mysql binlog 按表写入kafka不同topic
java·大数据·mysql·flink
Hello.Reader2 小时前
Flink SQL + Kafka 实时统计部门人数
sql·flink·kafka
♡喜欢做梦2 小时前
MyBatis操作数据库(进阶):动态SQL
java·数据库·sql·java-ee·mybatis
copyer_xyf2 小时前
SQL 语法速查手册:前端开发者的学习笔记
前端·数据库·sql
n***s9098 小时前
【MySQL基础篇】概述及SQL指令:DDL及DML
sql·mysql·oracle
jnrjian10 小时前
FRA中 keep的backup set 不保险
sql·oracle
h***047714 小时前
SpringBoot集成Flink-CDC,实现对数据库数据的监听
数据库·spring boot·flink