↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

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 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1 / 7.0 / 6.5 / 6.4
历史版本PostgreSQL 8.4 已于 2014 年 7 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

34.10. 用户定义的聚合 #

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 以及其他一些简单 聚合(如 max 和 min),只需把第一个 非空输入值插入状态变量,然后从第二个非空输入值开始应用转移 函数即可。如果初始条件为 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 之外,也可用于其他一些聚合。)

聚合函数可以使用多态的状态转移函数或最终函数,这样同一组函数 就能用于实现多个聚合。关于多态函数的解释,请参见 第 34.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 编写的函数可以通过检查函数调用的“上下文”是否被传入了一个 AggState或WindowAggState节点, 来检测自己是否作为聚合转移函数或最终函数被调用,例如:

        if (fcinfo->context &&
            (IsA(fcinfo->context, AggState) ||
             IsA(fcinfo->context, WindowAggState)))

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

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

提交更正

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