pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
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能够判断对于complex类型的列该使用哪一种 sum。)
如果没有非空输入值,上述sum定义将返回零(即初始状态值)。也许我们更希望在这种情况下返回空值 — SQL 标准要求sum这样做。实现这一点只需省略initcond短语,使初始状态值为空值。通常这意味着sfunc需要检查输入的状态值是否为空值。但对于sum以及max、min这类其他简单聚合,只需把第一个非空输入值放入状态变量,然后从第二个非空输入值开始应用转移函数即可。如果初始状态值为空值,并且转移函数被标记为“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,spcacl,spcoptions}
(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,aclitem[],text[]}
(1 row)
用 C 编写的函数可以通过调用AggCheckCallContext检测自己是否正作为聚合的转移函数或最终函数被调用,例如:
if (AggCheckCallContext(fcinfo, NULL))
进行这种检查的一个原因是:当它对转移函数为真时,第一个输入必须是临时的转移值,因此可以安全地就地修改,而无需分配新的副本。示例见int8inc()。 (这是函数修改按引用传递输入唯一安全的情况。特别要指出的是,聚合最终函数在任何情况下都不应修改其输入,因为在某些情况下它们会在同一个最终转移值上被重新执行。)
更多细节请参见CREATE AGGREGATE命令。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。