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

AggregateUnionTransposeRule 会把 Aggregate 下推到 UNION ALL 的每个输入之上,再在 union 顶部加一层汇总聚合把子结果合并回来。这和 AggregateJoinTransposeRule 的思路类似,都是先在分支内部做局部汇总,再做顶层合并。本文结合源码实现,分析它为什么只支持 UNION ALL、只支持一组明确列出的聚合函数,以及它为什么要特别关心各个 union 输入的列空值性差异。

1. 规则要解决什么问题

典型输入:

sql 复制代码
SELECT deptno, SUM(sal)
FROM (
  SELECT deptno, sal FROM t1
  UNION ALL
  SELECT deptno, sal FROM t2
) u
GROUP BY deptno;

规则可以把它改写成:

sql 复制代码
SELECT deptno, SUM(sum_sal)
FROM (
  SELECT deptno, SUM(sal) AS sum_sal FROM t1 GROUP BY deptno
  UNION ALL
  SELECT deptno, SUM(sal) AS sum_sal FROM t2 GROUP BY deptno
) x
GROUP BY deptno;

这样每个分支先本地聚合,顶部只合并局部结果。

2. 为什么只能用于 UNION ALL

源码注释给了一个非常直观的反例:如果是 UNION DISTINCT,先对子查询分别聚合再 union,和先 union 去重后再聚合,结果可能完全不同。

因此规则一开始就要求:

java 复制代码
if (!union.all) {
  return;
}

3. 为什么只支持有限几类聚合函数

源码里维护了一个 SUPPORTED_AGGREGATES

  • MIN/MAX
  • COUNT
  • SUM
  • SUM0
  • ANY_VALUE
  • 位运算聚合

只要出现:

  • DISTINCT
  • 不在白名单中的聚合函数

规则就直接退出。

这说明该规则只处理那些具有明确"局部聚合 + 顶层合并"性质的函数。

4. 为什么还要先检查所有输入是否已经唯一

源码会遍历 union 每个输入,检查其在当前 group set 上是否已经唯一。

如果所有输入都已经唯一,那么把 aggregate 下推到各输入上没有任何收益,还可能导致规则来回触发形成循环。因此会直接退出。

5. 子聚合是如何构造的

对于 union 每个输入,规则都会构造一个 child aggregate:

java 复制代码
relBuilder.push(input);
relBuilder.aggregate(relBuilder.groupKey(aggRel.getGroupSet()), childAggCalls);

也就是说,分支级聚合沿用原 group key,但聚合调用可能根据输入列的 nullability 重新推断类型。

6. 为什么要关心不同输入的空值性

源码有一个很重要的细节:

如果 union 某一列在整体 row type 中是 nullable,但某个具体输入里是 not null,或者反过来,那么把 aggregate 下推到子输入时,聚合返回类型可能和顶层不同。

因此它会在构造 child aggregate call 时,必要时重新创建 AggregateCall,让返回类型和当前输入环境匹配。这是为了避免 row type mismatch。

相关测试在 8500 行附近也专门覆盖了这一点。

7. 顶层为什么 COUNT 会变成 SUM0

transformAggCalls(...) 中,如果原始聚合是 COUNT,顶层不会继续用 COUNT,而是改成 SUM0

原因和 join transpose、merge 等规则类似:

  • 子聚合已经算出了每个分支的局部计数;
  • 顶层要做的是把这些局部计数加起来;
  • 因此语义上应是 SUM0(local_count)

8. 顶层 group key 为什么要重映射

子聚合之后,union 输出中的字段布局已经变成:

  • 前面是 group key;
  • 后面是每个子聚合产出的 measure 列。

因此顶部 aggregate 的 group key 必须被重新映射到新的列坐标系上。源码通过 INVERSE_SURJECTION mapping 完成这个重定位。

9. 代表性测试

RelOptRulesTest 中和这条规则直接相关的测试主要在:

  • 6000 行附近的基础用例;
  • 8500 行附近的类型、断言和唯一性边界用例。

这些测试覆盖了:

  • 正常 UNION ALL 下推;
  • row type mismatch 修复;
  • 子 aggregate 构造断言;
  • "如果所有输入已经唯一则不应下推"。

总结

AggregateUnionTransposeRule 的核心思路是:

  1. 仅对 UNION ALL 做局部聚合下推;
  2. 子分支先按原 group key 汇总;
  3. 顶层再对这些局部结果做二次聚合;
  4. COUNT、类型和空值性做额外修正。

它是一条典型的"先分支内压缩,再全局合并"的聚合下推规则。

相关推荐
一个儒雅随和的男子9 小时前
多租户方案的选型
数据库·oracle
真上帝的左手9 小时前
19. 大数据-概念
大数据·big data
listening77710 小时前
HarmonyOS 6.1 跨设备数据库实战:分布式账本的落地与一致性校验
数据库·harmonyos·分布式账本
法雅特吉他10 小时前
吉他日常保养完全指南:从环境参数到部位养护的系统化方案
大数据·经验分享·新媒体运营·学习方法·材质
xiancai_xianyu10 小时前
企业本体语义:设备保养与供应商评估,为什么需要统一的语义模型?
大数据·人工智能
shujudang10 小时前
企业级 APP 数据分析平台选型指南:业务评估与落地路径
大数据·数据挖掘·数据分析·移动开发
laboratory agent开发10 小时前
AI agent多知识源并存时,一致性为何总出偏差
大数据·人工智能
147API10 小时前
Claude Tag 进入 Slack 后,团队智能体需要哪些任务与审计字段
java·开发语言·数据库
Database_Cool_10 小时前
AI Agent 应用数据库选型:阿里云 PolarDB-X 高并发分布式数据底座
数据库·人工智能·阿里云
liuxiaowei310 小时前
Winform+WPF双框架实战:喷涂工艺SCADA上位机从0到1搭建(附采集监控源码+车间踩坑实录)
大数据·hadoop·wpf