【Calcite 系列】深入理解 Calcite 的 AggregateFilterTransposeRule

AggregateFilterTransposeRule 会把 Aggregate(Filter(input)) 改写成 Filter(Aggregate(input)),也就是把聚合下推到过滤之前。听起来这不像常规的"提早过滤"优化,但这条规则的目的本来就不是减少输入行数,而是让计划形态更适合物化视图匹配和某些高代价谓词场景。本文结合源码实现,分析它何时可以直接转置,何时必须拆成上下两层聚合,以及它为什么明确拒绝 distinct 和不可 rollup 的聚合函数。

1. 规则要解决什么问题

典型输入:

text 复制代码
Aggregate(group=[ename, sal, deptno])
  Filter(sal > 5000)
    Scan(emp)

规则希望得到:

text 复制代码
Filter(sal > 5000)
  Aggregate(group=[ename, sal, deptno])
    Scan(emp)

或者在更复杂场景下:

text 复制代码
Aggregate(top)
  Filter(...)
    Aggregate(bottom)
      Scan(...)

2. 为什么这条规则不以性能为第一目标

类注释说得很直接:这条规则不会直接减少工作量,因为聚合提早做了,可能反而处理更多行。

它更适合两类场景:

  1. 谓词本身很贵,希望先做聚合降低谓词计算次数;
  2. 需要把计划改写成和某个物化视图更接近的形态。

3. 匹配模式

规则匹配:

text 复制代码
Aggregate
  Filter

这是它和 AggregateFilterToFilteredAggregateRule 的一个重要区别:两者都从同一形态出发,但目标完全不同。

4. 第一阶段:判断 filter 依赖了哪些列

源码先取出:

  • filterColumns:过滤条件中使用的列;
  • newGroupSet = aggregate.getGroupSet().union(filterColumns)

意思是:如果想把 Aggregate 放到 Filter 下面,就必须保证 Filter 用到的列在新聚合输出中还看得见。

5. 为什么要做唯一性检查

源码里有一段很关键:

java 复制代码
final Boolean unique = mq.areColumnsUnique(input, newGroupSet);
if (unique != null && unique) {
  return;
}

这不是在做正确性判断,而是在防循环。

如果输入已经在 newGroupSet 上唯一,再套一个新的下推聚合几乎没意义,还可能导致规则不断往返触发,形成:

text 复制代码
A-F => A-F-A => A-A-F-A => ...

6. 两种改写路径

6.1 简单路径:filter 所需列都在原 group key 中

如果:

java 复制代码
allColumnsInAggregate == true
&& aggregate.getGroupType() == Group.SIMPLE

说明过滤条件所需的列本来就已经包含在聚合输出中。

这时可以直接构造:

  1. 一个基于 newGroupSet 的下层 aggregate;
  2. 一个在其上的 filter;
  3. 不再需要上层 aggregate。

这属于最干净的一种转置。

6.2 复杂路径:需要拆成上下两层 Aggregate

如果过滤条件依赖了原聚合不会输出的列,或者原聚合有 grouping sets,就不能直接停在 Filter(Aggregate(...))

因为:

  • 过滤条件要看见这些额外列;
  • 但最终输出又不能把这些列暴露出去;
  • 所以必须先在下层聚合保留这些列,过滤后再由上层聚合把粒度收回去。

7. 上层聚合为什么要求 rollup 能力

复杂路径里,规则会为每个原始 AggregateCall 取:

java 复制代码
aggregateCall.getAggregation().getRollup()

如果返回 null,规则直接放弃。

原因是:下层聚合先按更细粒度算了一次,上层必须有办法把这些"局部结果"再滚起来。没有 rollup 定义的聚合,无法安全地两阶段重组。

这也是为什么源码明确拒绝:

  • DISTINCT 聚合;
  • 没有 rollup 定义的聚合函数。

8. GROUPING SETS 场景怎么处理

如果原聚合不是 Group.SIMPLE,源码会重写每个 grouping set,把它们从原字段坐标映射到新的 newGroupSet 坐标上,再交给上层 aggregate。

也就是说,这条规则并不是只支持简单 group by。它支持 grouping sets,但代价是必须走"下层保留更多列,上层再收缩"的那条复杂路径。

9. 代表性测试

RelOptRulesTest 中这条规则的典型测试有:

  • testPullFilterThroughAggregate
  • testPullFilterThroughAggregateGroupingSets

第一条验证简单 group by 的转置;

第二条验证 rollup / grouping sets 场景下,规则仍然可以正确拆成多层 aggregate。

10. 总结

AggregateFilterTransposeRule 不是常见的"把 Filter 往下推"那类规则,而是反过来把 Aggregate 往下推。

它的核心不是局部性能,而是计划形态重写:

  1. 能直接转置时,就直接把聚合放到过滤前;
  2. 不能直接转置时,就用两层 aggregate 维持语义;
  3. 任何不能 rollup、带 distinct、或容易形成循环的情况,都宁可不改。

这是一条很典型的"为了等价形态匹配服务"的优化器规则。

相关推荐
小堃学编程2 小时前
【项目实战】基于protobuf的发布订阅式消息队列(1)—— 准备工作
java·大数据·开发语言
m0_518019482 小时前
使用Python操作文件和目录(os, pathlib, shutil)
jvm·数据库·python
轩情吖2 小时前
MySQL Connect
数据库·mysql·adb·select·连接·远程访问数据库
lifewange2 小时前
SQL中的聚合函数有哪些
android·数据库·sql
无忧智库2 小时前
破局与重构:大型集团财务共享业财一体化的数字基因革命(PPT)
大数据·架构
阿贵---2 小时前
使用Python进行PDF文件的处理与操作
jvm·数据库·python
稻草猫.2 小时前
MyBatis-Plus高效开发全攻略
java·数据库·后端·spring·java-ee·mybatis·mybatis-plus
人道领域2 小时前
Day | 09 【苍穹外卖:订单售后业务】
java·数据库·后端
minji...2 小时前
Linux 进程间通信(一)进程间通信与匿名管道
linux·运维·服务器·数据结构·数据库·c++