不能直接用SUM或ARRAY_AGG实现复杂业务逻辑,因其仅支持通用计算;需通过CREATE AGGREGATE自定义聚合,严格配置SFUNC、STYPE、INITCOND三要素,并确保状态确定性与函数安全性。

能,但必须分清状态函数、最终函数和聚合定义三者的职责边界,否则中间值错乱、结果不可复现是常态。
为什么不能直接用 SUM 或 ARRAY_AGG 解决你的业务逻辑
内置聚合函数只做通用计算:加总、拼接、计数。一旦业务规则涉及“每单起步价+里程单价×总公里数+四舍五入”这类带初始化值、中间状态累积、最后变形的流程,SUM 就无能为力了。比如出租车计费中,3.5 起步价不能参与循环累加,而要作为初始值注入;又比如百分位计算,需要先收集全部值再排序取索引——这些都不是单次行处理能完成的。
-
ARRAY_AGG只负责堆数据,不支持自定义初始化(INITCOND)、不支持对状态数组做后期加工(如排序、插值) - 强行用
STRING_AGG(DISTINCT ...)做去重拼接,在 PostgreSQL 14 之前不支持DISTINCT修饰符,必须靠子查询或自定义函数兜底 - 所有中间状态(如累计金额、原始数值列表)必须显式声明类型(
STYPE),类型不匹配会导致ERROR: function ... does not exist
CREATE AGGREGATE 的三个必填参数怎么配才不出错
聚合定义本身不写逻辑,只是把状态流转串起来。最容易漏掉或配反的是这三个:
-
SFUNC:状态转换函数,必须接受「上一次返回值」+「当前行字段值」+(可选)「外部传参」,返回新状态。例如taxi_accum(numeric, numeric, numeric)中,第一个numeric是上次结果,第二个是当前km,第三个是外部传入的单价2.20 -
STYPE:状态的数据类型,必须和SFUNC返回值、FINALFUNC输入参数严格一致。常见错误是写成numeric[]却在FINALFUNC里当numeric用 -
INITCOND:首次调用SFUNC时,给它的第一个参数赋的初值。不写就默认为NULL,若SFUNC内部没做COALESCE处理,整组结果就全NULL
调试时如何看到每一步中间值
PL/pgSQL 函数里用 RAISE NOTICE 是唯一可靠方式,比查日志快得多。关键点:
- 在
SFUNC开头打印$1(上一轮结果)、$2(当前行值)、$3(外部参数)和本次返回值,能立刻验证状态是否按预期累积 - 在
FINALFUNC开头打印$1(最终状态),确认进入该函数前数据完整无损 - 避免在
FINALFUNC里做耗时操作(如远程 HTTP 请求),它在每个分组末尾同步执行,会拖慢整个查询
性能与兼容性容易被忽略的细节
自定义聚合不是写完就能上线的。真实环境里这几个点常导致线上抖动:
- 状态类型选
numeric[]存大量值?PostgreSQL 会把它存成 TOAST 行外存储,频繁读写放大 I/O。小数据量用numeric累加,大数据量再切数组 - PostgreSQL 12+ 支持并行聚合,但你的
SFUNC和FINALFUNC必须标PARALLEL SAFE,否则自动降级为单线程 - 若聚合结果要进物化视图或分区表表达式索引,确保
FINALFUNC是 immutable(比如round()可以,now()不行)
最麻烦的从来不是写对语法,而是让状态在并发、中断、大结果集下依然可重现——这意味着每一步输入输出都要确定,不能依赖 session 变量或临时表。

















