【Calcite 系列】将 INTERSECT 转换为 EXISTS

1. 背景与动机 (Motivation)

在 SQL 标准中,INTERSECT(交集)是一个常见的集合操作符。默认情况下,INTERSECT 暗含 DISTINCT 语义,即返回同时存在于两个查询结果集中的唯一行。

在传统的执行引擎中,INTERSECT 通常通过以下两种方式实现:

  1. Hash Join / Merge Join:将两边数据进行全量 Join 并在之后进行去重。
  2. Sort + Unique:对两边结果集排序后进行比对去重。

然而,在很多场景下,特别是当左表(Left Input)较小而右表(Right Input)很大且有索引时,将 INTERSECT 转换为 EXISTS 子查询(即 Semi-Join 半连接)往往能获得巨大的性能提升。

为什么 EXISTS 更快?

  • 短路优化EXISTS 只需要找到第一条匹配的记录即可返回,无需处理右表的所有重复项。
  • 谓词下推 :优化器更容易将过滤条件推入 EXISTS 子查询中。
  • 索引利用EXISTS 转换后生成的 Semi-Join 可以直接触发索引扫描,避免全表集合运算。

基于此,CALCITE-6836 引入了 IntersectToExistsRule 规则。


2. 核心设计方案 (Design)

2.1 转换逻辑

该规则的目标是将 LogicalIntersect 节点转换为等价的 LogicalCorrelate(带 EXISTS)或 Semi-Join 结构。

对于多路交集(N-way Intersect):

sql 复制代码
SELECT columns FROM T1
INTERSECT
SELECT columns FROM T2
INTERSECT
SELECT columns FROM T3

会被改写为:

sql 复制代码
SELECT DISTINCT columns FROM T1
WHERE EXISTS (SELECT 1 FROM T2 WHERE T2.cols = T1.cols)
  AND EXISTS (SELECT 1 FROM T3 WHERE T3.cols = T1.cols)

2.2 限制条件

为了保证语义正确性,该规则仅在以下场景触发:

  1. 仅限 INTERSECT DISTINCT :不支持 INTERSECT ALL。因为 ALL 需要精确计数匹配次数,而 EXISTS 只关心"有无"。
  2. 列数与类型匹配:所有输入的字段数量和物理类型必须能够进行等值比较。

3. 实现细节剖析 (Implementation)

在 PR #4209 中,核心实现逻辑集中在新增的 IntersectToExistsRule 类中。

3.1 规则触发机制

规则匹配 LogicalIntersect 算子:

java 复制代码
// 核心匹配逻辑
public class IntersectToExistsRule extends RelOptRule {
    public static final IntersectToExistsRule INSTANCE = new IntersectToExistsRule(RelBuilderFactory.INSTANCE);

    private IntersectToExistsRule(RelBuilderFactory factory) {
        super(operand(LogicalIntersect.class, any()), factory, null);
    }

    @Override
    public void onMatch(RelOptRuleCall call) {
        LogicalIntersect intersect = call.rel(0);
        // 1. 过滤 INTERSECT ALL
        if (intersect.all) {
            return;
        }
        
        // 2. 转换开始...
        transform(call, intersect);
    }
}

3.2 树结构重组

通过 RelBuilder 递归地将每一个右侧输入(Input[1...N])包装成一个 EXISTS 子查询,并与第一个输入(Input[0])通过 Correlate 关联。

核心伪代码逻辑

java 复制代码
RelNode left = intersect.getInput(0);
List<RelNode> rights = intersect.getInputs().subList(1, n);

RelBuilder builder = call.builder();
builder.push(left);

for (RelNode right : rights) {
    // 为每一个交集项创建一个 CorrelationId
    CorrelationId id = cluster.createCorrel();
    
    // 构造内部 Filter 条件:T1.col1 = T2.col1 AND T1.col2 = T2.col2 ...
    builder.push(right);
    RexNode condition = builder.fields().stream()
        .map(f -> builder.equals(f, builder.field(id, f.getIndex())))
        .reduce(builder::and)
        .get();
        
    builder.filter(condition);
    // 包装成 EXISTS 语义
    builder.exists(false); 
    
    // 执行逻辑关联
    builder.join(JoinRelType.INNER, builder.literal(true));
}

// 最后处理 INTERSECT DISTINCT 语义
builder.distinct();
call.transformTo(builder.build());

4. 优化前后的执行计划对比

假设我们有两张表 EMPDEPT,执行 SELECT deptno FROM EMP INTERSECT SELECT deptno FROM DEPT

优化前

使用的是集合运算节点,通常会导致全量数据扫描和代价昂贵的 SetOp 实现。

text 复制代码
LogicalIntersect(all=[false])
  LogicalProject(deptno=[$7])
    LogicalTableScan(table=[[scott, EMP]])
  LogicalProject(deptno=[$0])
    LogicalTableScan(table=[[scott, DEPT]])

优化后

转换成了 Correlate 配合 EXISTS 过滤器。

text 复制代码
LogicalAggregate(group=[{0}])
  LogicalCorrelate(correlation=[$cor0], joinType=[inner], variant=[exists])
    LogicalProject(deptno=[$7])
      LogicalTableScan(table=[[scott, EMP]])
    LogicalFilter(condition=[=($0, $cor0.deptno)])
      LogicalTableScan(table=[[scott, DEPT]])

注:此计划随后可以进一步被 SemiJoinRule 优化为标准的物理 SemiJoin 算子。


5. 测试验证 (Testing)

PR 中包含了完善的单元测试(RelOptRulesTest.xml),涵盖了:

  • 基础两路交集 :验证 INTERSECT 转换为 EXISTS 的基本正确性。
  • 多路交集:测试 A ∩ B ∩ C 的链式转换。
  • NULL 语义测试 :确保 INTERSECT 对 NULL 的处理(NULLs are equal for intersection)在转换后依然保持一致。
  • 负案例 :确保 INTERSECT ALL 不会被错误触发。

参考资料

相关推荐
言慢行善21 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星21 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟21 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z21 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可21 小时前
Java 中的实现类是什么
java·开发语言
He少年21 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新21 小时前
myeclipse的pojie
java·ide·myeclipse
迷藏4941 天前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏4941 天前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
qq_433502181 天前
Codex cli 飞书文档创建进阶实用命令 + Skill 创建&使用 小白完整教程
java·前端·飞书