第 12 章 扩展 SQL:聚合
PostgreSQL 中的聚合函数被表示为状态值和状态转换函数。也就是说,聚合可以按照每当处理一个输入项就被修改的状态来定义。要定义一个新的聚合函数,需要为状态值选择一种数据类型、为状态选择一个初始值,以及一个状态转换函数。状态转换函数只是一个普通函数,也可以在聚合上下文之外使用。还可以指定一个最终函数,以防聚合的期望输出与需要保存在运行状态值中的数据不同。
因此,除了聚合的用户所能看到的输入和结果数据类型之外,还有一个内部的状态值数据类型,它可能与输入和结果类型都不同。
如果我们定义一个不使用最终函数的聚合,得到的就是一个对每行列值计算运行函数的聚合。Sum
就是这类聚合的一个例子。Sum
从零开始,总是把当前行的值加到它的运行总和上。例如,如果我们想让
sum
聚合作用于复数的数据类型,我们只需要该数据类型的加法函数。聚合定义是:
CREATE AGGREGATE complex_sum (
sfunc = complex_add,
basetype = complex,
stype = complex,
initcond = '(0,0)'
);
SELECT complex_sum(a) FROM test_complex; complex_sum ------------- (34,53.9)
(实践中我们只会把该聚合命名为
sum,让
PostgreSQL
自行判断对
complex 类型的列应用哪种
sum。)
如果没有非空的输入值,上面
sum
的定义将返回零(初始状态条件)。也许我们希望在这种情形下改为返回
NULL——SQL
标准期望
sum
表现出那样的行为。要做到这一点,只需省略
initcond
子句,使初始状态条件为
NULL。通常这意味着
sfunc
需要检查 NULL
的状态条件输入,但对于
sum
以及
max、min
等一些简单聚合,只需把第一个非空输入值插入状态变量,然后从第二个非空输入值开始应用转换函数就足够了。当初始条件为
NULL 且转换函数被标记为
“strict”(即不对 NULL
输入调用)时,PostgreSQL
会自动完成这一工作。
“strict” 转换函数的另一项默认行为是:每当遇到 NULL 输入值时,先前的状态值保持不变。这样,NULL 值就被忽略了。如果你需要 NULL 输入有其他行为,只需把转换函数定义为非 strict,并让它测试 NULL 输入并做任何合适的处理。
Avg(平均值)是聚合的一个更复杂的例子。它需要两个运行状态:输入的总和以及输入的数量。最终结果通过这两个量相除得到。平均值通常用二元数组作为转换状态值来实现。例如,avg(float8)
的内置实现如下所示:
CREATE AGGREGATE avg (
sfunc = float8_accum,
basetype = float8,
stype = float8[],
finalfunc = float8_avg,
initcond = '{0,0}'
);
更多细节参见参考手册中对
CREATE
AGGREGATE 命令的描述。