跳转到主要内容

03 · 行集合上的聚合

场景

在数据块下方追加一行合计页脚,对其上方的数据块求和。或者把跨源聚合(例如全公司总和)拉进表头单元格。

裸括号聚合 — 作用于数据块

{{ SUM([续约金额]) }}
{{ COUNT([续约金额]) }}
{{ AVERAGE([续约金额]) }}
{{ MIN([续约金额]) }}
{{ MAX([续约金额]) }}

数据块里使用时,这些函数会沿正在迭代的源行累加。在页脚行(数据块下方的行,数据块所在的行不含模板块)里使用时,同一表达式指代刚刚展开完成的数据块。

| A1: 客户 | B1: 续约金额 |
| A2: {{ [客户] }}| B2: {{ [续约金额] }} | ← 数据块
| A3: 合计 | B3: {{ SUM([续约金额]) }}| ← 页脚

按 3 条源数据展开后:第 3 行变为第 5 行,B5 显示三个 续约金额 的总和。

源限定聚合 — 作用于整张源

{{ SUM(续约记录[金额]) }} # 整张源,而非活动数据块
{{ COUNT(客户名单[客户]) }}

写成 SUM(SourceName[Column]) 时,xl3 会对整张指定源求和——而不是过滤或连接后的数据块。这种形态适合表头里那种"总合计"单元格,即使数据块被过滤也不应改变其值。

续约记录__sources__ 中声明的名字。参考 Recipe 07

过滤只改变数据块,不改变源

{{ @filter [区域] = "北京" }}
{{ [客户] }} | {{ [续约金额] }}
合计: | {{ SUM([续约金额]) }} # 仅北京行
全部: | {{ SUM(Source[续约金额]) }} # 全部行

SUM([续约金额]) 反映的是过滤后的数据块。SUM(Source[续约金额]) 忽略过滤条件。

不支持的形态 — 聚合内的算术

SUMAVERAGEMINMAX 以及单参数 COUNT 的唯一参数必须是列引用([Column]Source[Column])。在聚合参数里写按行算术、字面量、函数调用,会在解析期被拒绝,错误码 xl3/eval/bad-aggregate-arg(ADR-0059):

{{ SUM([数量] * [单价]) }} # ✗ 按行算术 — 被拒绝
{{ SUM(1 + 2) }} # ✗ 字面量表达式 — 被拒绝
{{ SUM(IF([区域]="北京", [金额], 0)) }} # ✗ 函数调用 — 被拒绝
{{ AVERAGE([销售额] - [成本]) }} # ✗ 按行相减 — 被拒绝

这是有意为之。Excel 的 SUMPRODUCT / 数组公式语义(按行计算再聚合)在 XTL 0.x 的范围之外——参考 ADR-0059 § "Why not allow SUM([a] + [b])"。

解决方法:在上游加一列辅助列

经典模式(销售额 = Σ 数量 × 单价)的标准写法是在源工作簿里加一列保存按行的乘积,然后对该列求和:

# 在源数据中追加 "金额" 列:
| 数量 | 单价 | 金额 |
| 3 | 100 | =B2*C2 (或预计算的 300) |
| 2 | 150 | =B3*C3 (或 300) |

# 在模板中:
{{ SUM([金额]) }} # ✓ — 对预计算列求和

如果源是程序生成的,就把乘积结果直接写入这一列。如果源是手工维护的工作簿,那就在 金额 列里用普通 Excel 公式。

解决方法:行级单元格 + 页脚聚合

如果你只需要按行展示乘积(不需要求和),可以在模板单元格里逐行计算:

| {{ [数量] }} | {{ [单价] }} | {{ [数量] * [单价] }} | # ✓ 行级

这样能跑通,因为 {{ [数量] * [单价] }} 是按迭代行求值的。但它不等同于 SUM([数量] * [单价])——如果还想在页脚得到这些乘积的总和,回到上面"加辅助列"的方案(或在页脚单元格里写原生 Excel SUMPRODUCT 公式,xl3 按 ADR-0046 原样保留)。

备注

  • 聚合按 ADR-0007 忽略空值。
  • COUNT 只统计非空值。如果想统计包括空在内的所有行,请对永远非空的列使用 COUNT(Source[某必填列])
  • 对 0 个非空值求 AVERAGE 返回空(empty),不报错。
  • 聚合参数里出现复合表达式(字面量、算术、函数调用)会按 ADR-0059 抛出 xl3/eval/bad-aggregate-arg
  • 规范参考:spec/language.md "Aggregates";源语义参考 ADR-0012;参数形态规则参考 ADR-0059。