Flink SQL 中的 SELECT & WHERE,批流统一的查询入口

1. 前言:所有复杂查询都从 SELECT 开始

无论你在 Flink 上跑的是离线批任务,还是实时流式任务,一切都从 SELECT 开始

  • 从 Kafka / 文件 / 数据库等源读取数据;
  • 选出你关心的字段;
  • 加上一些计算逻辑;
  • 再用 WHERE 过滤掉不需要的数据。

很多人一上来就研究窗口、Join、UDF,其实最常用、最"耐用"的语句,就是这俩:SELECTWHERE

理解了 Flink SQL 里的 SELECT / WHERE,也就打好了写复杂实时分析 SQL 的地基。

下面我们就从最基础的语法入手,一点点展开。

2. SELECT 的基础语法与 table_expression

Flink SQL 中,SELECT 的一般形式是:

sql 复制代码
SELECT select_list
FROM table_expression
[ WHERE boolean_expression ]

这里面有三个核心元素:

  1. select_list:选哪些列、做哪些计算;
  2. table_expression:数据从哪里来;
  3. WHERE boolean_expression:在结果出来前怎么过滤。
2.1 table_expression:不只是"表"

文档里提到,table_expression 可以是"任何数据来源",具体包括:

  • Catalog 中的物理表(例如 Kafka、JDBC、Filesystem 等 Connector 定义的表);
  • 视图CREATE VIEW / CREATE TEMPORARY VIEW 定义的逻辑表);
  • VALUES / VALUE 子句创建的一小段内联数据;
  • 多表 JOIN 后的结果;
  • 子查询(FROM (SELECT ...) AS t)。

比如,最简单的从一张表读数据:

sql 复制代码
SELECT *
FROM Orders;

也可以从 join 后的结果读:

sql 复制代码
SELECT o.order_id, c.name
FROM Orders AS o
JOIN Customers AS c
  ON o.customer_id = c.id;

还可以从子查询结果中再查:

sql 复制代码
SELECT user_id, total_amount
FROM (
  SELECT user_id, SUM(price) AS total_amount
  FROM Orders
  GROUP BY user_id
) AS t;

在 Flink 里,无论是 batch 还是 streaming,这些写法都是统一的 SQL 语义,区别只在于:

  • 底层的数据源是有界(batch)还是无界(stream);
  • 规划器在执行计划上选择的是批执行算子还是流执行算子。

3. select_list:为什么不建议用 *

很多人最开始写 SQL 都习惯:

sql 复制代码
SELECT *
FROM Orders;

在调试、临时查询时,这非常方便------"全部拉出来先看看再说"。

但在生产 SQL 里,文档明确建议:不要用 *

3.1 使用 * 的问题
  1. 对 schema 变更敏感

    • 如果下游代码假设存在某些字段,而上游表 schema 改了(字段改名/删除),* 可能会导致隐性错误;
    • 比如多取了你不需要的字段,或者字段顺序变了,某些工具/解析逻辑可能出问题。
  2. 不利于性能调优

    • 尤其在大宽表场景,SELECT * 会把所有字段都读出来,即使你只用到了其中 2~3 个;
    • 这会增加网络传输、序列化/反序列化成本。
  3. 可读性差

    • 当别人看到一个复杂查询中的 SELECT *,很难一眼看出"这条 SQL 到底想要什么"。
3.2 推荐写法:明确列 + 计算表达式

文档中的建议写法是这样的:

sql 复制代码
SELECT order_id, price + tax
FROM Orders;

你可以做几件事情:

  • 直接选择字段:order_id
  • 做表达式计算:price + tax
  • 起别名:price + tax AS total_price
  • 调用函数:UPPER(user_name) AS user_name_upper

示例:

sql 复制代码
SELECT
  order_id,
  price,
  tax,
  price + tax AS total_price
FROM Orders;

这样做的好处:

  • 一眼就能知道"这个查询要的是什么数据";
  • 上游 schema 调整时,更有感知、可控;
  • 对下游接口、报表、实时指标都更友好。

4. 使用 VALUES 子句构造内联数据

在很多场景里,你可能想:

  • 快速构造一些测试数据;
  • 做一个小"维表"来参与 JOIN;
  • 不想单独建表,只是临时用一下。

这时候就可以使用 VALUES 子句:

sql 复制代码
SELECT order_id, price
FROM (VALUES (1, 2.0), (2, 3.1)) AS t (order_id, price);

拆解一下:

  • (VALUES (1, 2.0), (2, 3.1)):构造出两行数据:

    • 第 1 行:(1, 2.0)
    • 第 2 行:(2, 3.1)
  • AS t (order_id, price)

    • 给这个内联表起名 t
    • 并给两列命名为 order_idprice

结果等价于你创建了一张两行的小表。

典型用途:

  1. 调试 SQL

    在没有真实源表时,快速验证函数逻辑、表达式计算:

    sql 复制代码
    SELECT price, tax, price + tax AS total
    FROM (VALUES (100, 10), (200, 30)) AS v(price, tax);
  2. 小范围"编码表"

    比如定义状态码映射(真的很少条时):

    sql 复制代码
    WITH status_dim AS (
      SELECT *
      FROM (VALUES
        (0, 'INIT'),
        (1, 'PAID'),
        (2, 'CANCELLED')
      ) AS t(status_code, status_name)
    )
    SELECT o.order_id, d.status_name
    FROM Orders AS o
    LEFT JOIN status_dim AS d
      ON o.status = d.status_code;

在 Flink SQL 中,这种写法在 batch / streaming 下都能用,尤其适合 SQL-CLI 的快速调试。

5. WHERE 子句:在数据进入下游前先"瘦身"

WHERE 是最常用的过滤手段,语法非常直观:

sql 复制代码
SELECT price + tax
FROM Orders
WHERE id = 10;

这里 WHERE id = 10 就是一个布尔表达式 boolean_expression

你可以在里面做各种组合:

  • 比较:=<>>>=<<=
  • 逻辑运算:ANDORNOT
  • 区间:BETWEEN ... AND ...
  • 集合:IN (...)
  • 模糊匹配:LIKENOT LIKE 等;
  • 处理空值:IS NULLIS NOT NULL

示例:

sql 复制代码
SELECT order_id, price
FROM Orders
WHERE price > 100
  AND status = 'PAID'
  AND country = 'CN';

在流式场景下,WHERE 还有一个很重要的作用------减小下游压力

  • 越早过滤不需要的记录,下游 Join / 聚合算子压力越小;
  • 对于维表 Lookup Join、Window Aggregation 等算子尤为重要。

6. 在 SELECT 中使用内置函数和 UDF

Flink SQL 支持在 SELECT 里调用各种函数:

  • 内置标量函数(Scalar Function),如:

    • 字符串函数:UPPER, LOWER, SUBSTRING, TRIM...
    • 日期时间函数:CURRENT_TIMESTAMP, DATE_FORMAT...
    • 数学函数:ABS, ROUND, CEIL...
  • 用户自定义标量函数(UDF)。

文档中的例子是:

sql 复制代码
SELECT PRETTY_PRINT(order_id)
FROM Orders;

这里 PRETTY_PRINT 就是一个函数,可以是内置的,也可以是你注册的 UDF。

6.1 使用 UDF 的步骤(概念层面)
  1. 实现 UDF(在 Java/Scala 中)

    例如定义一个格式化订单号的函数;

  2. 将 UDF 注册到 catalog 中

    在 Flink SQL 环境中,使用类似:

    sql 复制代码
    CREATE FUNCTION PRETTY_PRINT AS 'com.example.PrettyPrintFunction';
  3. 在 SQL 中正常调用

    sql 复制代码
    SELECT PRETTY_PRINT(order_id) AS pretty_id
    FROM Orders;

这种模式非常适合把复杂的业务逻辑从 SQL 中抽出去,封装成可复用的函数。

7. 综合示例:从 Kafka 流中过滤并格式化订单数据

下面给一个偏实战一点的小例子,假设我们有一张 Kafka 源表:

sql 复制代码
CREATE TABLE Orders (
  order_id   STRING,
  user_id    STRING,
  price      DOUBLE,
  tax        DOUBLE,
  country    STRING,
  status     STRING,
  order_time TIMESTAMP(3),
  WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'orders',
  'properties.bootstrap.servers' = 'localhost:9092',
  'scan.startup.mode' = 'latest-offset',
  'format' = 'json'
);

我们希望:

  • 只关心已支付订单 status = 'PAID'
  • 只看中国用户 country = 'CN'
  • 计算订单总金额 price + tax
  • 顺便把订单号格式化一下(假设有 UDF FORMAT_ORDER_ID)。

可以这样写:

sql 复制代码
SELECT
  FORMAT_ORDER_ID(order_id) AS pretty_order_id,
  user_id,
  price,
  tax,
  price + tax AS total_amount,
  order_time
FROM Orders
WHERE status = 'PAID'
  AND country = 'CN';

这里你能同时看到:

  • FROM Orders:数据来自 Kafka 流;
  • SELECT 里既有原始列,又有表达式和 UDF 调用;
  • WHERE 提前把不关心的记录过滤掉。

在流式 job 中,这条 SQL 会持续消费新数据,并实时输出满足条件的记录。

8. 小结

这篇文章围绕 Flink SQL 中最基础但最常用的两个部分:SELECTWHERE,做了一个系统梳理:

  1. SELECT 负责从 table_expression 中取出你真正关心的字段
  2. table_expression 可以是表、视图、VALUES、join 后的结果或子查询;
  3. 实际生产中尽量避免 SELECT *,推荐明确列名 + 计算表达式;
  4. VALUES 子句在调试和构造小型维表时非常有用;
  5. WHERE 是第一道过滤关,既简化结果,也减轻下游压力;
  6. 内置函数和 UDF 则让 SELECT 不只是"取字段",而是"做计算 + 显示逻辑";
  7. 所有这些,在 Flink 的 批处理与流处理 场景中语法是一致的。
相关推荐
白露与泡影1 小时前
Kafka:消费者重试与死信队列的对应模式分析
分布式·kafka·linq
雨落秋垣10 小时前
手搓 Java 的用户行为跟踪系统
java·开发语言·linq
Elastic 中国社区官方博客13 小时前
ES|QL 在 9.2:智能查找连接和时间序列支持
大数据·数据库·人工智能·sql·elasticsearch·搜索引擎·全文检索
q***062913 小时前
LangChain-08 Query SQL DB 通过GPT自动查询SQL
数据库·sql·langchain
吴五万14 小时前
SQL Server 表分区脚本
sql
超龄超能程序猿15 小时前
提升文本转SQL(Text-to-SQL)精准度的实践指南
数据库·人工智能·sql
阿里云大数据AI技术16 小时前
MaxCompute SQL AI:让 SQL 成为你的 AI 语言
人工智能·sql
阿里云大数据AI技术16 小时前
MaxCompute SQL AI:让SQL成为你的AI语言
人工智能·sql
va学弟18 小时前
初识SQL
数据库·sql