全部产品
Search
文档中心

实时计算Flink版:维表关联

更新时间:Sep 11, 2026

本文将为您介绍维表关联的使用。

维表关联(Lookup Join)

  • Lookup Join是维表 Join的一种实现方式,详情请参见维表JOIN语句。

  • 仅Fluss的主键表支持作为维表进行join,且join条件需包含维表的全部主键。如果建表时将Bucket Key设置为join key,join条件也可以是主键的前缀子集,详情请参见基于Bucket Key的维表查询。

  • 维表为分区主键表时,join条件还需包含分区键。

  • Fluss的维表关联默认使用异步模式,吞吐更高,可通过 SQL Hint设置'lookup.async' = 'false',将维表关联改成同步模式。

关联两个维表的SQL示例

创建两个维表

USE CATALOG `fluss_catalog`;
USE `my_db`;

CREATE TABLE `orders` (
  `o_orderkey` INT NOT NULL,
  `o_custkey` INT NOT NULL,
  `o_orderstatus` CHAR(1) NOT NULL,
  `o_totalprice` DECIMAL(15, 2) NOT NULL,
  `o_orderdate` DATE NOT NULL,
  `o_orderpriority` CHAR(15) NOT NULL,
  `o_clerk` CHAR(15) NOT NULL,
  `o_shippriority` INT NOT NULL,
  `o_comment` STRING NOT NULL,
  `o_dt` STRING NOT NULL,
  PRIMARY KEY (o_orderkey) NOT ENFORCED
);

CREATE TABLE `customer` (
  `c_custkey` INT NOT NULL,
  `c_name` STRING NOT NULL,
  `c_address` STRING NOT NULL,
  `c_nationkey` INT NOT NULL,
  `c_phone` CHAR(15) NOT NULL,
  `c_acctbal` DECIMAL(15, 2) NOT NULL,
  `c_mktsegment` CHAR(10) NOT NULL,
  `c_comment` STRING NOT NULL,
  PRIMARY KEY (c_custkey) NOT ENFORCED
);

两个维表关联

CREATE TEMPORARY TABLE `lookup_join_sink` (
  `order_key`        INT NOT NULL,
  `order_totalprice` DECIMAL(15, 2) NOT NULL,
  `customer_name`    STRING NOT NULL,
  `customer_address` STRING NOT NULL
) WITH ('connector' = 'blackhole');

-- 异步模式(默认)
INSERT INTO `lookup_join_sink`
SELECT `o`.`o_orderkey`, `o`.`o_totalprice`, `c`.`c_name`, `c`.`c_address`
FROM (SELECT `orders`.*, PROCTIME() AS `ptime` FROM `orders`) AS `o`
LEFT JOIN `customer` FOR SYSTEM_TIME AS OF `o`.`ptime` AS `c`
ON `o`.`o_custkey` = `c`.`c_custkey`;

同步模式关联

-- 同步模式
INSERT INTO `lookup_join_sink`
SELECT `o`.`o_orderkey`, `o`.`o_totalprice`, `c`.`c_name`, `c`.`c_address`
FROM (SELECT `orders`.*, PROCTIME() AS `ptime` FROM `orders`) AS `o`
LEFT JOIN `customer` /*+ OPTIONS('lookup.async' = 'false') */ FOR SYSTEM_TIME AS OF `o`.`ptime` AS `c`
ON `o`.`o_custkey` = `c`.`c_custkey`;

分区表维表关联

维表为分区主键表时,join条件需包含全部主键,即主键与分区键。

-- 主键为 (c_custkey, dt),分区键为 dt
CREATE TABLE `customer_partitioned` (
  `c_custkey` INT NOT NULL,
  `c_name` STRING NOT NULL,
  `c_address` STRING NOT NULL,
  `c_nationkey` INT NOT NULL,
  `c_phone` CHAR(15) NOT NULL,
  `c_acctbal` DECIMAL(15, 2) NOT NULL,
  `c_mktsegment` CHAR(10) NOT NULL,
  `c_comment` STRING NOT NULL,
  `dt` STRING NOT NULL,
  PRIMARY KEY (`c_custkey`, `dt`) NOT ENFORCED
)
PARTITIONED BY (`dt`)
WITH (
  'table.auto-partition.enabled' = 'true',
  'table.auto-partition.time-unit' = 'year'
);

join条件中除主键外,还需带上分区键。

INSERT INTO `lookup_join_sink`
SELECT `o`.`o_orderkey`, `o`.`o_totalprice`, `c`.`c_name`, `c`.`c_address`
FROM (SELECT `orders`.*, PROCTIME() AS `ptime` FROM `orders`) AS `o`
LEFT JOIN `customer_partitioned` FOR SYSTEM_TIME AS OF `o`.`ptime` AS `c`
ON `o`.`o_custkey` = `c`.`c_custkey` AND `o`.`o_dt` = `c`.`dt`;

分区表的详情请参见数据分区。

基于Bucket Key的维表查询(Prefix Lookup)

功能概述

主键表作为维表时,如果建表时将Bucket Key设置为join key,join条件可以是维表主键的前缀子集,无需包含全部主键,即Prefix Lookup。

Prefix Lookup默认使用异步模式,吞吐更高,可通过 SQL Hint设置'lookup.async' = 'false',改成同步模式。

使用限制

  • 仅Fluss的主键表支持作为维表进行Prefix Lookup。

  • 建表时必须将Bucket Key设置为join key。Bucket Key的设置方式请参见数据分桶。

  • join条件必须是维表主键的前缀子集。分区主键表的join条件为主键前缀(不含分区键)加分区键。

  • 不支持与lookup.insert-if-not-exists同时使用。

使用示例

分区表

维表为分区主键表时,join条件为主键前缀(不含分区键)加分区键。以下示例中维表主键为 (c_custkey, c_nationkey, dt),Bucket Key为 c_custkey,分区键为 dt。

-- 主键为 (c_custkey, c_nationkey, dt),Bucket Key 为 c_custkey,分区键为 dt
CREATE TABLE `customer_partitioned_with_bucket_key` (
  `c_custkey` INT NOT NULL,
  `c_name` STRING NOT NULL,
  `c_address` STRING NOT NULL,
  `c_nationkey` INT NOT NULL,
  `c_phone` CHAR(15) NOT NULL,
  `c_acctbal` DECIMAL(15, 2) NOT NULL,
  `c_mktsegment` CHAR(10) NOT NULL,
  `c_comment` STRING NOT NULL,
  `dt` STRING NOT NULL,
  PRIMARY KEY (`c_custkey`, `c_nationkey`, `dt`) NOT ENFORCED
)
PARTITIONED BY (`dt`)
WITH (
  'bucket.key' = 'c_custkey',
  'table.auto-partition.enabled' = 'true',
  'table.auto-partition.time-unit' = 'year'
);
-- join条件为主键前缀(不含分区键)+ 分区键
INSERT INTO `lookup_join_sink`
SELECT `o`.`o_orderkey`, `o`.`o_totalprice`, `c`.`c_name`, `c`.`c_address`
FROM (SELECT `orders`.*, PROCTIME() AS `ptime` FROM `orders`) AS `o`
LEFT JOIN `customer_partitioned_with_bucket_key` FOR SYSTEM_TIME AS OF `o`.`ptime` AS `c`
ON `o`.`o_custkey` = `c`.`c_custkey` AND `o`.`o_dt` = `c`.`dt`;

非分区表

以下示例中维表主键为 (c_custkey, c_nationkey),Bucket Key为 c_custkey,join条件仅使用主键前缀 c_custkey。

-- 主键为 (c_custkey, c_nationkey),Bucket Key 为 c_custkey
CREATE TABLE `customer_with_bucket_key` (
  `c_custkey` INT NOT NULL,
  `c_name` STRING NOT NULL,
  `c_address` STRING NOT NULL,
  `c_nationkey` INT NOT NULL,
  `c_phone` CHAR(15) NOT NULL,
  `c_acctbal` DECIMAL(15, 2) NOT NULL,
  `c_mktsegment` CHAR(10) NOT NULL,
  `c_comment` STRING NOT NULL,
  PRIMARY KEY (`c_custkey`, `c_nationkey`) NOT ENFORCED
) WITH (
  'bucket.key' = 'c_custkey'
);
-- Prefix Lookup,异步模式(默认)
INSERT INTO `lookup_join_sink`
SELECT `o`.`o_orderkey`, `o`.`o_totalprice`, `c`.`c_name`, `c`.`c_address`
FROM (SELECT `orders`.*, PROCTIME() AS `ptime` FROM `orders`) AS `o`
LEFT JOIN `customer_with_bucket_key` FOR SYSTEM_TIME AS OF `o`.`ptime` AS `c`
ON `o`.`o_custkey` = `c`.`c_custkey`;
-- Prefix Lookup,同步模式
INSERT INTO `lookup_join_sink`
SELECT `o`.`o_orderkey`, `o`.`o_totalprice`, `c`.`c_name`, `c`.`c_address`
FROM (SELECT `orders`.*, PROCTIME() AS `ptime` FROM `orders`) AS `o`
LEFT JOIN `customer_with_bucket_key` /*+ OPTIONS('lookup.async' = 'false') */ FOR SYSTEM_TIME AS OF `o`.`ptime` AS `c`
ON `o`.`o_custkey` = `c`.`c_custkey`;

历史分区查询(Historical Partition Lookup)

功能概述

自动分区会按保留策略删除过期分区。分区删除后,即使数据已分层至Paimon,引用该分区的维表关联也查不到数据。

开启历史分区查询后,主键Lookup在原始分区不存在时回退至Paimon查询。在维表上开启table.datalake.historical-partition.enabled即可。

使用限制

  • 默认关闭,需在维表上开启table.datalake.historical-partition.enabled。

  • 仅支持已开启自动分区、且只有一个分区键的Paimon主键表。

  • 开启后Coordinator会创建并保留__historical__系统分区,用于将Lookup路由至Paimon。关闭该配置会删除该系统分区。

  • Lookup客户端使用Lookuper创建时获取的表配置判断是否回退。修改配置后,需重启需要查询历史分区的Lookup作业。

使用示例

在维表上开启历史分区查询。

ALTER TABLE `customer_partitioned_with_bucket_key` SET (
  'table.datalake.historical-partition.enabled' = 'true'
);

参数详情请参见Connector 参数。

Insert If Not Exists

功能概述

在执行 Lookup Join 时,如果查找键在维表中没有匹配的行,默认行为是跳过关联(对于 LEFT JOIN,维表侧返回 NULL)。通过启用 lookup.insert-if-not-exists 选项,当查找未命中时,Fluss 会自动插入一条以查找键为主键的新行,并将新插入的行作为关联结果返回。

该功能特别适合与主键表结合使用,在流处理过程中自动构建字典表。典型场景是将高基数的字符串标识符(如用户 ID、设备 ID)映射为紧凑的整数 ID,用于下游高效的 Aggregation Merge Engine。

使用限制

  • 仅支持主键 Lookup,不支持 Prefix Lookup。

  • 维表中除主键列和自增列外,不能包含非空(NOT NULL)列。因为自动插入时 Fluss 无法为这些列填充值。

  • 通过 SQL Hint 启用:/*+ OPTIONS('lookup.insert-if-not-exists' = 'true') */

使用示例

以下示例演示如何在 Lookup Join 中自动构建 UID 字典表。

  1. 创建带自增列的字典表。

CREATE TABLE uid_mapping (
  uid VARCHAR NOT NULL,
  uid_int32 INT,PRIMARY KEY (uid) NOT ENFORCED
) WITH ('auto-increment.fields' = 'uid_int32','bucket.num' = '1');
  1. 执行带 insert-if-not-exists 的 Lookup Join。当某个 uid 首次出现时,Fluss 自动将其插入 uid_mapping 并分配一个自增的 uid_int32 值。

-- 流表 ods_events 中的 uid 自动注册到字典表 uid_mapping,-- 并获取对应的整型 ID uid_int32SELECT
  ods.country,
  ods.prov,
  ods.city,
  ods.ymd,
  ods.uid,
  dim.uid_int32
FROM ods_events AS ods
JOIN uid_mapping /*+ OPTIONS('lookup.insert-if-not-exists' = 'true') */FOR SYSTEM_TIME AS OF ods.proctime AS dim
  ON dim.uid = ods.uid;

假设 ods_events 包含以下数据:

country

prov

city

ymd

uid

CN

Beijing

Haidian

2025-01-01

user_a

CN

Shanghai

Pudong

2025-01-02

user_b

US

California

LA

2025-01-03

user_a

JP

Tokyo

Shibuya

2025-01-04

user_c

关联结果为:

country

prov

city

ymd

uid

uid_int32

CN

Beijing

Haidian

2025-01-01

user_a

1

CN

Shanghai

Pudong

2025-01-02

user_b

2

US

California

LA

2025-01-03

user_a

1

JP

Tokyo

Shibuya

2025-01-04

user_c

3

  • user_a 首次出现时获得 uid_int32 = 1,第二次出现时复用相同值。

  • user_b 和 user_c 各获得一个新的自增 ID。

作业运行后,uid_mapping 字典表内容为:

uid

uid_int32

user_a

1

user_b

2

user_c

3

相关文档