选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.0 已于 2015 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

35.10. User-Defined Aggregates #

PostgreSQL 中的聚合函数是依据 状态值状态转移函数 来定义的。也就是说,聚合通过一个状态值来工作,并在处理每个 后续输入行时更新该状态值。要定义一个新的聚合函数,需要为状态值 选定一种数据类型、为状态选定一个初始值,并指定一个状态转移 函数。状态转移函数只是普通函数,也可以在聚合的上下文之外使用。 如果聚合的期望结果不同于运行状态值中需要保存的数据,还可以指定 一个最终函数

因此,除了聚合用户所看到的参数和结果数据类型之外,还存在一种内部状态值数据类型,它可能既不同于参数类型,也不同于结果类型。

如果我们定义一个不使用最终函数的聚合,那么得到的就是一个对每 一行的列值进行逐步计算的聚合。sum 就是这种聚合的 一个示例。sum 从零开始,并始终把当前行的值加到 运行总和中。例如,如果我们希望让 sum 聚合能用于 复数数据类型,那么只需要该数据类型的加法函数。聚合定义如下:

CREATE AGGREGATE sum (complex)
(
    sfunc = complex_add,
    stype = complex,
    initcond = '(0,0)'
);

SELECT sum(a) FROM test_complex;

   sum
-----------
 (34,53.9)

(注意,这里我们依赖的是函数重载:名为 sum 的聚合 不止一个,但 PostgreSQL 能判断出哪种 sum 适用于 complex 类型的列。)

如果没有非空输入值,上面这个 sum 的定义会 返回零(初始状态条件)。也许我们希望在这种情况下返回 null —— SQL 标准就期望 sum 这样表现。只需省略 initcond 短语,使初始状态条件为 null,就可以 做到这一点。通常这意味着 sfunc 需要检查空 状态条件输入。但对于 sum 以及其他一些简单 聚合(如 maxmin),只需把第一个 非空输入值插入状态变量,然后从第二个非空输入值开始应用转移 函数即可。如果初始条件为 null 且转移函数被标记为 strict(即不为空输入调用), PostgreSQL 会自动完成这项工作。

strict转移函数的另一项默认行为是,只要遇到空值输入,就会保留先前的状态值不变。因此,空值会被忽略。如果你需要对空值输入采用其他行为,就不要把转移函数声明为 strict,而应在函数代码中自行检测空值输入并完成所需处理。

avg(平均值)是更复杂的聚合示例。它需要两个运行 状态:输入的总和与输入的个数。最终结果由二者相除得到。平均值 通常用数组作为状态值来实现。例如,avg(float8) 的内置实现如下所示:

CREATE AGGREGATE avg (float8)
(
    sfunc = float8_accum,
    stype = float8[],
    finalfunc = float8_avg,
    initcond = '{0,0,0}'
);

float8_accum 需要一个三元素数组,而不只是两个 元素,因为它累积的不仅是输入值的和与计数,还有平方和。这样它 除了可用于 avg 之外,也可用于其他一些聚合。)

聚合函数可以使用多态的状态转移函数或最终函数,这样同一组函数 就能用于实现多个聚合。关于多态函数的解释,请参见 第 35.2.5 节。再进一步,聚合函数自身 也可以指定为具有多态输入类型和状态类型,从而让同一个聚合定义 服务于多种输入数据类型。下面是一个多态聚合的示例:

CREATE AGGREGATE array_accum (anyelement)
(
    sfunc = array_append,
    stype = anyarray,
    initcond = '{}'
);

对于任何给定的聚合调用,其实际状态类型都是一种数组类型,该 数组以实际输入类型为元素类型。这个聚合的行为是把所有输入串接 成这种类型的一个数组。(注意:内置聚合 array_agg 提供了类似的功能,而且性能比这个定义 更好。)

下面是以两种不同的实际数据类型作为参数时的输出:

SELECT attrelid::regclass, array_accum(attname)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |              array_accum              
---------------+---------------------------------------
 pg_tablespace | {spcname,spcowner,spclocation,spcacl}
(1 row)

SELECT attrelid::regclass, array_accum(atttypid::regtype)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |        array_accum        
---------------+---------------------------
 pg_tablespace | {name,oid,text,aclitem[]}
(1 row)

用 C 编写的函数可以通过调用 AggCheckCallContext 来检测自己是否作为聚合转移函数或最终函数被调用,例如:

if (AggCheckCallContext(fcinfo, NULL))

检查这一点的一个原因是,如果对状态转移函数来说结果为真,那么 第一个输入一定是临时转移值,因此可以安全地原地修改,而不必 分配一个新副本。 示例见 int8inc()。(这是函数可以安全地修改按引用 传递的输入的唯一情况。特别是,聚合的最终函数在任何 情况下都不得修改其输入,因为在某些情况下,它们会在同一个最终 转移值上被再次执行。)

更多细节请参见CREATE AGGREGATE命令。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。