ARRAY_AGG需显式加ORDER BY子句保证顺序,否则不保证元素顺序;默认保留NULL,可用WHERE或FILTER过滤;推荐JSONB构造嵌套结构;大数据量时注意内存溢出和性能瓶颈。

ARRAY_AGG 会把多行变成一个数组,但默认不保证顺序
直接用 ARRAY_AGG(column) 能聚合,但 PostgreSQL 不保证结果数组内元素的顺序和原始查询顺序一致——哪怕你写了 ORDER BY 在外层。这是最容易踩的坑:看起来偶尔对,其实不可靠。
必须显式加 ORDER BY 子句到 ARRAY_AGG() 内部:
SELECT user_id, ARRAY_AGG(tag ORDER BY created_at) AS tags FROM user_tags GROUP BY user_id;
注意:ORDER BY 是 ARRAY_AGG 的修饰子句,不是窗口函数语法,也不支持别名引用(比如不能写 ORDER BY t.created_at 如果 t 是表别名)。
处理 NULL 值:ARRAY_AGG 默认保留 NULL,想过滤得先 WHERE 或用 FILTER
如果某行 tag 是 NULL,它会被原样放进数组。不想让 NULL 出现在结果里,有两个办法:
- 在
WHERE子句中提前排除:WHERE tag IS NOT NULL - 用
FILTER子句(PostgreSQL 9.4+)更精准:ARRAY_AGG(tag) FILTER (WHERE tag IS NOT NULL)
FILTER 的优势是不影响其他聚合或分组逻辑,适合多字段混合聚合场景。比如同时要统计总数和非空标签数组:
SELECT user_id, COUNT(*) AS total_tags, ARRAY_AGG(tag) FILTER (WHERE tag IS NOT NULL) AS non_null_tags FROM user_tags GROUP BY user_id;
嵌套结构聚合:ARRAY_AGG 配合 ROW() 或 JSONB 构造复合数据
单纯聚合标量值简单,但实际常需带字段名的结构,比如把 (name, role) 对打包成数组。这时不能直接 ARRAY_AGG((name, role))——那会得到 record 数组,可读性差且难解构。
推荐转成 JSONB 更实用:
SELECT
dept_id,
ARRAY_AGG(
JSONB_BUILD_OBJECT('name', name, 'role', role)
ORDER BY role
) AS members
FROM employees
GROUP BY dept_id;
或者用 ROW() + 显式类型转换(需声明目标类型,否则可能报错):
ARRAY_AGG(ROW(name, role)::TEXT ORDER BY role)
但 JSONB 方案兼容性更好、前端也容易消费,除非有强类型校验需求,否则优先选它。
性能注意:大结果集上 ARRAY_AGG 可能内存溢出或变慢
ARRAY_AGG 会在内存中构建整个数组,如果某组数据动辄几万行,不仅慢,还可能触发 ERROR: out of memory(尤其 work_mem 设得小的时候)。
优化方向:
- 确认是否真需要全部聚合——有时用
LIMIT+ 子查询分页更合适 - 加大会话级
work_mem:SET LOCAL work_mem = '64MB';(仅对当前查询生效) - 避免在
ARRAY_AGG里做复杂表达式计算,先在子查询中算好
另外,ARRAY_AGG 不支持并行执行(截至 PG 15),大数据量分组聚合时,CPU 利用率会是单核瓶颈。

















