array_agg(distinct ...)是最直接的去重拼接方式,postgresql 10+原生支持,语法简洁、性能好,返回数组类型便于后续处理;需注意null排序、空格处理及显式order by保序。

ARRAY_AGG(DISTINCT ...) 是最直接的去重拼接方式
PostgreSQL 10+ 原生支持在 ARRAY_AGG 内部使用 DISTINCT,无需子查询或数组展开,语法简洁且性能好。它天然适配分组后对字符串字段做“去重 + 聚合成数组”这一需求。
常见错误是写成 ARRAY_AGG(DISTINCT tag ORDER BY tag) 却忽略:若 tag 含 NULL,默认排序会把 NULL 排在最前,可能打乱业务预期。需显式加 NULLS LAST:
ARRAY_AGG(DISTINCT tag ORDER BY tag NULLS LAST)- 若字段是文本但含空格,建议先
trim(tag)再去重,否则'VIP'和'VIP '会被视为不同值 - 不加
ORDER BY时,结果顺序不确定——即使源数据有序,ARRAY_AGG也不保证保序
为什么不用 STRING_AGG(DISTINCT ...) 直接拼接?
STRING_AGG 确实也支持 DISTINCT(如 STRING_AGG(DISTINCT tag, ',')),但它只能输出字符串,丢失了后续处理能力。而 ARRAY_AGG(DISTINCT ...) 返回数组类型,可立刻配合 array_to_string、array_length 或下标取值(如 (arr)[1])做进一步计算。
典型场景包括:
- 需要同时返回去重后的字符串和去重后元素个数:
array_to_string(ARRAY_AGG(DISTINCT tag), ',')和array_length(ARRAY_AGG(DISTINCT tag), 1) - 要取第一个去重值当主标签:
(ARRAY_AGG(DISTINCT tag ORDER BY priority DESC))[1] - 后续要在应用层反序列化为列表(如 Python 的
json.loads)——数组比逗号字符串更结构化
遇到 GROUP BY 字段多、去重逻辑复杂怎么办?
当去重要基于多个字段组合(比如 (user_id, tag, source) 唯一才算真重复),或需按优先级过滤(如保留 source = 'system' 的 tag,再 fallback 到 'manual'),DISTINCT 就不够用了。
这时应把去重逻辑上推到输入层,用 CTE 或子查询预处理:
- 先
SELECT DISTINCT ON (user_id, tag) *按业务规则选一行 - 或用
ROW_NUMBER() OVER (PARTITION BY user_id, tag ORDER BY CASE source WHEN 'system' THEN 1 ELSE 2 END)标记优先级 - 再对外层结果调用
ARRAY_AGG(tag)——此时输入已干净,无需再DISTINCT
硬在 ARRAY_AGG 里套 UNNEST + DISTINCT + ARRAY 是绕远路,性能差、可读性低,仅在极少数兼容旧版本(
Greenplum 或旧版 PostgreSQL 不支持 DISTINCT 怎么办?
Greenplum 大部分版本(6.x+)已支持 ARRAY_AGG(DISTINCT ...),但若确认不支持(如 GP 5.x 或极老 PG),唯一可靠路径是子查询去重:
SELECT user_id, ARRAY_AGG(tag) AS tags FROM (SELECT DISTINCT user_id, tag FROM user_tags) t GROUP BY user_id;
注意两点:
- 子查询必须包含所有
GROUP BY字段(这里是user_id),否则外层GROUP BY会报错 - 若还需排序,得在子查询里
ORDER BY user_id, tag,但 PostgreSQL 不保证子查询排序能传递给外层聚合——稳妥做法是在ARRAY_AGG中再次加ORDER BY tag
这种写法虽兼容性好,但执行计划多一层嵌套,大数据量时 I/O 和内存开销明显高于原生 DISTINCT 支持。










