【Flink SQL】Flink SQL 语法篇(六):Temporal Join

本文详细介绍了FlinkSQL中的TemporalJoin和VersionedTable概念,重点讨论了两种定义VersionedTable的方法,以及如何在事件时间和处理时间场景下进行TemporalJoin操作,包括汇率计算示例。

Flink SQL 语法篇》系列,共包含以下 10 篇文章:

😊 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 🚀🚀🚀 吧 (点赞 🧡、关注 💛、收藏 💚)!!!您的支持 💖💖💖 将激励 🔥 博主输出更多优质内容!!!

Temporal Join 定义(支持 Batch / Streaming):Temporal Join 在离线的概念中其实是没有类似的 Join 概念的,但是离线中常常会维护一种表叫做 拉链快照表,使用一个明细表去 Join 这个 拉链快照表 的 Join 方式就叫做 Temporal Join。而 Flink SQL 中也有对应的概念,表叫做 Versioned Table,使用一个明细表去 Join 这个 Versioned Table 的 Join 操作就叫做 Temporal Join。Temporal Join 中,Versioned Table 其实就是对同一条 key(在 DDL 中以 Primary Key 标记同一个 key)的历史版本(根据时间划分版本)做一个维护,当有明细表 Join 这个表时,可以根据明细表中的时间版本选择 Versioned Table 对应时间区间内的快照数据进行 Join。

应用场景:比如常见的汇率数据(实时的根据汇率计算总金额),在 12 : 00 12:00 12:00 之前(事件时间),人民币和美元汇率是 7 : 1 7:1 7:1,在 12 : 00 12:00 12:00 之后变为 6 : 1 6:1 6:1,那么在 12 : 00 12:00 12:00 之前数据就要按照 7 : 1 7:1 7:1 进行计算, 12 : 00 12:00 12:00 之后就要按照 6 : 1 6:1 6:1 计算。在事件时间语义的任务中,事件时间 12 : 00 12:00 12:00 之前的数据,要按照 7 : 1 7:1 7:1 进行计算, 12 : 00 12:00 12:00 之后的数据,要按照 6 : 1 6:1 6:1 进行计算。这其实就是离线中快照的概念,维护具体汇率的表在 Flink SQL 体系中就叫做 Versioned Table

1.Versioned Table 的两种定义方式

Verisoned Table:Verisoned Table 中存储的数据通常是来源于 CDC 或者会发生更新的数据。Flink SQL 会为 Versioned Table 维护 Primary Key 下的所有历史时间版本的数据。举一个汇率场景的案例来看一下一个 Versioned Table 的两种定义方式。

1.1 PRIMARY KEY 定义方式

-- 定义一个汇率 versioned 表,其中 versioned 表的概念下文会介绍到
CREATE TABLE currency_rates (
    currency STRING,
    conversion_rate DECIMAL(32, 2),
    update_time TIMESTAMP(3) METADATA FROM `values.source.timestamp` VIRTUAL,
    WATERMARK FOR update_time AS update_time,
    -- PRIMARY KEY 定义方式
    PRIMARY KEY(currency) NOT ENFORCED
) WITH (
   'connector' = 'kafka',
   'value.format' = 'debezium-json',
   /* ... */
);

1.2 Deduplicate 定义方式

-- 定义一个 append-only 的数据源表
CREATE TABLE currency_rates (
    currency STRING,
    conversion_rate DECIMAL(32, 2),
    update_time TIMESTAMP(3) METADATA FROM `values.source.timestamp` VIRTUAL,
    WATERMARK FOR update_time AS update_time
) WITH (
    'connector' = 'kafka',
    'value.format' = 'debezium-json',
    /* ... */
);

-- 将数据源表按照 Deduplicate 方式定义为 Versioned Table
CREATE VIEW versioned_rates AS
SELECT currency, conversion_rate, update_time   -- 1. 定义 `update_time` 为时间字段
  FROM (
      SELECT *,
      ROW_NUMBER() OVER (PARTITION BY currency  -- 2. 定义 `currency` 为主键
         ORDER BY update_time DESC              -- 3. ORDER BY 中必须是时间戳列
      ) AS rownum 
      FROM currency_rates)
WHERE rownum = 1; 

2.应用案例

Temporal Join 支持的时间语义:事件时间、处理时间。

2.1 案例一(事件时间)

-- 1. 定义一个输入订单表
CREATE TABLE orders (
    order_id    STRING,
    price       DECIMAL(32,2),
    currency    STRING,
    order_time  TIMESTAMP(3),
    WATERMARK FOR order_time AS order_time
) WITH (/* ... */);

-- 2. 定义一个汇率 versioned 表,其中 versioned 表的概念下文会介绍到
CREATE TABLE currency_rates (
    currency STRING,
    conversion_rate DECIMAL(32, 2),
    update_time TIMESTAMP(3) METADATA FROM `values.source.timestamp` VIRTUAL,
    WATERMARK FOR update_time AS update_time,
    PRIMARY KEY(currency) NOT ENFORCED
) WITH (
   'connector' = 'kafka',
   'value.format' = 'debezium-json',
   /* ... */
);

SELECT 
     order_id,
     price,
     currency,
     conversion_rate,
     order_time,
FROM orders
-- 3. Temporal Join 逻辑
-- SQL 语法为:FOR SYSTEM_TIME AS OF
LEFT JOIN currency_rates FOR SYSTEM_TIME AS OF orders.order_time
ON orders.currency = currency_rates.currency;

结果如下,可以看到相同的货币汇率会根据具体数据的事件时间不同 Join 到对应时间的汇率:

order_id  price  货币       汇率             order_time
========  =====  ========  ===============  =========
o_001     11.11  EUR       1.14             12:00:00
o_002     12.51  EUR       1.10             12:06:00
  • 事件时间的 Temporal Join 一定要给左右两张表都设置 Watermark。
  • 事件时间的 Temporal Join 一定要把 Versioned Table 的主键包含在 Join on 的条件中。

2.2 案例二(处理时间)

10:15> SELECT * FROM LatestRates;

currency   rate
======== ======
US Dollar   102
Euro        114
Yen           1

10:30> SELECT * FROM LatestRates;

currency   rate
======== ======
US Dollar   102
Euro        114
Yen           1

-- 10:42 时,Euro 的汇率从 114 变为 116
10:52> SELECT * FROM LatestRates;

currency   rate
======== ======
US Dollar   102
Euro        116     <====114 变为 116
Yen           1

-- 从 Orders 表查询数据
SELECT * FROM Orders;

amount currency
====== =========
     2 Euro             <== 在处理时间 10:15 到达的一条数据
     1 US Dollar        <== 在处理时间 10:30 到达的一条数据
     2 Euro             <== 在处理时间 10:52 到达的一条数据

-- 执行关联查询
SELECT
  o.amount, o.currency, r.rate, o.amount * r.rate
FROM
  Orders AS o
  JOIN LatestRates FOR SYSTEM_TIME AS OF o.proctime AS r
  ON r.currency = o.currency

-- 结果如下:
amount currency     rate   amount*rate
====== ========= ======= ============
     2 Euro          114          228    <== 在处理时间 10:15 到达的一条数据
     1 US Dollar     102          102    <== 在处理时间 10:30 到达的一条数据
     2 Euro          116          232    <== 在处理时间 10:52 到达的一条数据

可以发现处理时间就比较好理解了,因为处理时间语义中是根据左流数据到达的时间决定拿到的汇率值。Flink 就只为 LatestRates 维护了最新的状态数据,不需要关心历史版本的数据。

<think>好的,我现在需要回答用户关于Flink SQL和标准SQL语法差异的问题。首先,用户之前已经问过Flink SQL与传统SQL的区别,现在更具体地询问语法上的不同。我需要确保这次回答集中在语法层面,而不是重复之前的整体对比。 首先,我需要回忆一下之前提供的回答,其中提到了数据模型、时间语义、执行模式等方面,但用户现在更关注语法差异。因此,我需要调整重点,专注于具体的语法元素,比如DDL、DML、函数、窗口操作等。 用户提供的引用[1]是关于Flink SQL语法系列文章,其中提到WITH、SELECT、WHERE、窗口聚合等内容。这些内容可以作为参考,但需要结合标准SQL的语法进行对比。例如,Flink SQL的窗口函数如TUMBLE、HOP等,在标准SQL中并不存在,这是重要的差异点。 接下来,我需要组织回答的结构。可能分为以下几个部分:数据定义语言(DDL)、数据操作语言(DML)、时间处理和窗口函数、函数扩展、连接操作、动态与持续查询、执行控制语句等。每个部分对比Flink SQL和标准SQL的具体语法差异,并给出示例。 例如,在DDL方面,Flink SQL需要定义水印和时间属性,这在标准SQL中是没有的。在DML中,Flink SQL的SELECT语句可能涉及窗口聚合和状态管理。时间处理部分需要详细说明事件时间和处理时间的语法,以及水印的定义方法。 另外,用户提到要生成相关问题,我需要确保在最后列出相关问题,帮助用户深入理解。同时,引用文献需要正确标注,比如引用[^1]来自用户提供的资料。 需要确保回答符合之前的格式要求:使用中文,行内公式用$...$,独立公式用$$...$$,代码块正确标注,结构清晰。同时,检查是否有遗漏的重要语法差异,比如Top N的实现、Deduplication语法、临时等。 最后,验证示例代码的正确性,比如创建时定义水印,使用TUMBLE窗口,以及MATCH_RECOGNIZE的示例是否符合Flink SQL的语法规范。同时,确保引用的部分正确标注来源,如引用[^1]和[^2]对应的内容是否存在,避免虚构引用。 总结思考步骤:确定用户需求集中在语法差异,参考之前的回答和用户提供的引用,组织结构,分点对比,加入示例代码,验证正确性,生成相关问题,确保格式和引用正确。</think>### Flink SQL 与标准 SQL 语法差异对比 Flink SQL 在兼容标准 SQL 的基础上,扩展了流处理特有的语法(如时间语义、窗口操作、动态等),同时优化了部分语法结构以适应分布式计算场景。以下是核心语法差异的详细对比: --- #### **一、数据定义语言(DDL)差异** | **语法特性** | **标准 SQL** | **Flink SQL** | |----------------------|-------------------------------------------|--------------------------------------------| | **定义中的时间属性** | 无内置时间字段定义 | 必须显式定义事件时间(`TIMESTAMP(3)`)及水印(`WATERMARK`)[^1] | | **连接器声明** | 依赖数据库引擎(如 `CREATE TABLE` 不带存储类型) | 需指定连接器类型(如 `connector='kafka'`)[^1] | | **临时生命周期** | 持久化存储 | 任务级临时(任务停止后自动销毁) | **示例:定义含水印的动态** ```sql CREATE TABLE orders ( order_id STRING, amount DECIMAL, event_time TIMESTAMP(3), -- 定义水印(允许5秒延迟) WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'orders_topic' ); ``` --- #### **二、数据操作语言(DML)差异** | **语法特性** | **标准 SQL** | **Flink SQL** | |----------------------|-------------------------------------------|--------------------------------------------| | **窗口聚合语法** | 无原生支持(需手动分组) | 必须使用窗口函数(如 `TUMBLE`/`HOP`/`SESSION`) | | **持续查询** | 仅执行一次性查询 | 支持 `INSERT INTO` 持续输出结果到动态 | | **状态保留设置** | 无状态管理语法 | 可通过 `WITH` 参数控制状态生命周期(如 `'state.ttl'='7d'`) | **示例:滚动窗口聚合** ```sql SELECT user_id, TUMBLE_START(event_time, INTERVAL '1' HOUR) AS window_start, COUNT(*) AS click_count FROM clicks GROUP BY user_id, TUMBLE(event_time, INTERVAL '1' HOUR); ``` --- #### **三、时间语义与窗口函数** | **语法元素** | **标准 SQL** | **Flink SQL** | |----------------------|-------------------------------------------|--------------------------------------------| | **时间字段类型** | 普通时间戳字段(如 `TIMESTAMP`) | 需区分事件时间(`TIMESTAMP(3)`)和处理时间(`PROCTIME()`)[^1] | | **窗口定义方式** | 无 | 显式调用窗口函数(如 `TUMBLE`/`HOP`)或通过 `OVER` 子句定义 | | **延迟数据处理** | 无 | 通过 `WATERMARK` 和 `ALLOW_LATENESS` 处理乱序事件 | **示例:处理迟到数据** ```sql SELECT window_start, SUM(amount) FROM TABLE( TUMBLE( TABLE orders, DESCRIPTOR(event_time), INTERVAL '10' MINUTE, ALLOW_LATENESS(INTERVAL '2' MINUTE) -- 允许迟到2分钟 ) ) GROUP BY window_start; ``` --- #### **四、函数与操作符扩展** | **功能** | **标准 SQL** | **Flink SQL** | |----------------------|-------------------------------------------|--------------------------------------------| | **时间函数** | 基础函数(如 `NOW()`) | 流式时间函数(如 `CURRENT_WATERMARK`) | | **复杂事件处理** | 无 | `MATCH_RECOGNIZE` 语法(类正则模式匹配)[^1] | | **Top-N 查询** | 使用 `LIMIT` 或 `ROW_NUMBER()` | 专用 `TOP N` 语法(优化状态存储效率) | **示例:模式匹配检测连续失败登录** ```sql SELECT * FROM login_events MATCH_RECOGNIZE ( PARTITION BY user_id ORDER BY event_time MEASURES START_ROW.event_time AS start_time, LAST(FAIL.event_time) AS end_time PATTERN (START FAIL{3}) -- 连续3次失败 DEFINE FAIL AS FAIL.status = 'failure' ); ``` --- #### **五、连接(Join)操作差异** | **特性** | **标准 SQL** | **Flink SQL** | |----------------------|-------------------------------------------|--------------------------------------------| | **流连接类型** | 仅支持静态 `INNER JOIN`/`LEFT JOIN` | 支持流式连接(如 `Interval Join`/`Temporal Join`)[^1] | | **时间区间连接** | 无 | `Interval Join`(基于时间范围关联事件) | | **维关联** | 通过 `JOIN` 静态 | `LOOKUP JOIN`(动态查询外部存储) | **示例:Interval Join 关联订单与物流信息** ```sql SELECT o.order_id, l.logistics_time FROM orders o JOIN logistics l ON o.order_id = l.order_id AND l.logistics_time BETWEEN o.event_time AND o.event_time + INTERVAL '2' HOUR; ``` --- #### **、动态与持续查询** | **语法特性** | **标准 SQL** | **Flink SQL** | |----------------------|-------------------------------------------|--------------------------------------------| | **查询结果输出** | 一次性返回结果集 | 持续更新结果(如物化视图) | | **更新操作** | 显式 `UPDATE`/`DELETE` | 通过 `CHANGELOG` 模式隐式处理(如 `RETRACT` 流) | | **结果类型** | 仅支持静态结果 | 支持 `APPEND`/`RETRACT`/`UPSERT` 流 | --- ### 总结 Flink SQL 语法在以下方向显著区别于标准 SQL: 1. **时间驱动**:强制定义时间语义与水印机制,支持事件时间处理。 2. **流式扩展**:窗口函数、`MATCH_RECOGNIZE`、流式 `JOIN` 等语法专为无界数据流设计。 3. **状态管理**:通过 `WITH` 参数控制状态生命周期,避免资源泄漏。 4. **执行模式**:支持持续查询与动态结果,适应实时计算场景。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大数据与AI实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值