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. 为什么这条规则不以性能为第一目标
类注释说得很直接:这条规则不会直接减少工作量,因为聚合提早做了,可能反而处理更多行。
它更适合两类场景:
- 谓词本身很贵,希望先做聚合降低谓词计算次数;
- 需要把计划改写成和某个物化视图更接近的形态。
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
说明过滤条件所需的列本来就已经包含在聚合输出中。
这时可以直接构造:
- 一个基于
newGroupSet的下层 aggregate; - 一个在其上的 filter;
- 不再需要上层 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 中这条规则的典型测试有:
testPullFilterThroughAggregatetestPullFilterThroughAggregateGroupingSets
第一条验证简单 group by 的转置;
第二条验证 rollup / grouping sets 场景下,规则仍然可以正确拆成多层 aggregate。
10. 总结
AggregateFilterTransposeRule 不是常见的"把 Filter 往下推"那类规则,而是反过来把 Aggregate 往下推。
它的核心不是局部性能,而是计划形态重写:
- 能直接转置时,就直接把聚合放到过滤前;
- 不能直接转置时,就用两层 aggregate 维持语义;
- 任何不能 rollup、带 distinct、或容易形成循环的情况,都宁可不改。
这是一条很典型的"为了等价形态匹配服务"的优化器规则。