多个聚合函数可直接共存于同一SELECT中,前提是所有非聚合字段均出现在GROUP BY中;需注意NULL处理、STRING_AGG()和array_agg()的ORDER BY语法位置、去重限制、多列分组粒度及性能优化。

多个聚合函数能直接写在同一个SELECT里
可以,而且这是 PostgreSQL 最常规的用法。只要所有非聚合字段都出现在 GROUP BY 中,你就能在一条 SELECT 里同时用 COUNT()、SUM()、AVG()、MAX()、STRING_AGG() 等任意组合。
常见错误现象是执行时报错:column "xxx" must appear in the GROUP BY clause or be used in an aggregate function —— 这说明你 SELECT 了某个没聚合也没分组的列。
- 必须确保:每个出现在
SELECT列表中的列,要么被某个聚合函数包裹(如AVG(salary)),要么原样出现在GROUP BY子句中(如GROUP BY department_id, job_title) - 允许混合使用不同语义的聚合:比如既要人数(
COUNT(*)),又要平均薪资(AVG(salary)),还要最高薪资(MAX(salary)),全写在一行 SELECT 就行 - 注意 NULL 处理:所有标准聚合函数(
SUM、AVG、MAX、MIN)默认忽略 NULL;但COUNT(*)统计所有行,COUNT(col)只统计该列非 NULL 行
STRING_AGG() 和 array_agg() 要小心排序和去重
STRING_AGG() 和 array_agg() 是最常被误用的“多重聚合”成员,因为它们支持 ORDER BY 子句,但这个子句不能写在外部 ORDER BY 里,必须嵌在函数调用内。
典型错误是写成:SELECT department_id, STRING_AGG(name, ', ') ORDER BY name FROM employees GROUP BY department_id —— 这会报语法错误,因为 ORDER BY 在这里属于整个查询,不是函数参数。
- 正确写法是:
STRING_AGG(name, ', ' ORDER BY name),ORDER BY必须紧跟在分隔符后面、括号内 - 如果需要去重,不能直接加
DISTINCT到STRING_AGG()参数里(PostgreSQL 不支持),得先用子查询或 CTE 去重,再聚合 -
array_agg()同理,也支持ORDER BY,但不支持DISTINCT;若要数组去重,得套一层ARRAY(SELECT DISTINCT ...)
GROUP BY 多列时,聚合结果粒度由分组键完全决定
当你写 GROUP BY a, b,那每一行结果就对应唯一的一组 (a,b) 值。这时候所有聚合函数都是在这个更细的粒度上计算的——这点容易被忽略,尤其当后续想“向上汇总”时。
比如你查 SELECT year, month, COUNT(*), AVG(amount) FROM sales GROUP BY year, month,得到的是每月数据;但如果某个月缺数据,那一整行就没了,不会自动补 0 或合并到年维度。
- 若需多级汇总(如同时看月度和年度),别硬凑多个
GROUP BY,改用GROUPING SETS、ROLLUP或CUBE -
ROLLUP(year, month)会自动产出:每(年,月)、每(年)、总计三类聚合行,避免写多个 UNION - 注意
GROUPING()函数可用来识别哪些维度是“空汇总”(即被 rollup 掉的),方便标记“合计”行
性能敏感场景下,避免在聚合函数里套复杂表达式
看起来无害的写法,比如 STRING_AGG(COALESCE(first_name, '') || ' ' || COALESCE(last_name, ''), '; ' ORDER BY last_name),在百万级分组里可能显著拖慢查询——因为每行都要做字符串拼接 + 排序,且无法利用索引。
真实业务中,这类聚合往往是报表尾部“明细展开”用的,但一旦数据量上去,就成了瓶颈。
- 优先考虑把拼接逻辑移到应用层,数据库只返回原子字段,由代码组装
- 若必须在库内完成,确认目标字段上有合适索引(例如对
last_name单独建索引,能加速ORDER BY阶段) - 对超长文本聚合(如日志拼接),警惕内存溢出:
work_mem设置过小会导致落盘排序,大幅降低性能
多重聚合本身不难,难的是分清“谁在哪个粒度上算”以及“哪些操作实际发生在聚合前/后”。最容易被忽略的是:聚合函数内部的 ORDER BY 是独立作用域,和外层排序无关;还有就是 GROUP BY 的列组合一旦定死,所有聚合结果就被锁死在这个切片维度上,没法临时“升维”或“降维”——想灵活切换,得靠 GROUPING SETS 这类显式声明。

















