【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、或容易形成循环的情况,都宁可不改。

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

相关推荐
奇特認3 小时前
mysql 集群技术 3.高可用之 MHA
数据库·mysql
Bioinfo Guy5 小时前
高分Panel复现系列|转录因子网络太乱?用模块框把重点圈出来
数据库·生物信息学·生信可视化
leisoo80975 小时前
100GBA股股票数据怎么存ClickHouseRedisMySQLJSON完整对比
大数据·linux·服务器·开发语言·python
AI产品测评官6 小时前
突破系统割裂困局:2026企业级AI招聘架构如何迈向“原生全流程”?
大数据·微服务·架构
梦梦代码精6 小时前
连锁品牌数字化:从门店扩张到用户资产运营的技术底座
大数据·人工智能·低代码·docker·开源·代码规范
ltl7 小时前
Disaggregated DB 合集:Socrates、PolarDB、Taurus 的共同模式
数据库
Blossom i9 小时前
大数据预处理与采集实验一:使用Python操作MySQL数据库
数据库·mysql
沐梯子9 小时前
具身智能爆发前夜的“数据饥渴”:2026机器人数据采集产业链选型解析
大数据·人工智能·机器人
ltqvibe9 小时前
企业数智化中台的五层架构——AI框架为什么需要纵向贯通
大数据·人工智能·架构
雾时之林10 小时前
Linux--软件管理、源码包安装
linux·服务器·数据库