本文将为您介绍维表关联的使用。
维表关联(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 字典表。
创建带自增列的字典表。
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');执行带
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 |
相关文档
维表关联支持的配置项,详情请参见Connector 参数。
Bucket Key的设置方式,详情请参见数据分桶。
自动分区与分区管理策略,详情请参见数据分区。