STRING_AGG去重必须在聚合前显式处理:PostgreSQL支持STRING_AGG(DISTINCT col, ','),MySQL和SQL Server需用子查询或CTE先DISTINCT再聚合,SQLite不支持该函数;ARRAY_AGG去重需配合UNNEST或ARRAY_DISTINCT,灵活性高但性能较低。

STRING_AGG 去重必须先 DISTINCT 再聚合
直接对字段用 STRING_AGG(col, ',') 不会去重,重复值照常拼接。想得到“每个分组内唯一字符串的逗号拼接”,得在聚合前显式去重——但注意:不是在 STRING_AGG 里加 DISTINCT(部分数据库不支持),而是靠子查询或窗口预处理。
PostgreSQL 支持 STRING_AGG(DISTINCT col, ','),但 MySQL 和 SQL Server 不支持该语法;SQLite 根本没有 STRING_AGG。所以跨库写法要绕开。
- PostgreSQL 可安全用:
STRING_AGG(DISTINCT name, ',') - MySQL 必须用子查询:
SELECT group_id, GROUP_CONCAT(DISTINCT name SEPARATOR ',') FROM (SELECT DISTINCT group_id, name FROM t) t1 GROUP BY group_id - SQL Server 需搭配
STRING_AGG+ CTE 或派生表去重,否则重复项保留
ARRAY_AGG 去重后转字符串更可控
ARRAY_AGG 本身不提供去重能力,但它返回数组结构,后续可结合 ARRAY_DISTINCT(Trino/StarRocks)或 UNNEST + DISTINCT(PostgreSQL)再聚合,灵活性高于纯字符串函数。
典型 PostgreSQL 写法:
SELECT group_id,
STRING_AGG(DISTINCT elem, ',')
FROM (
SELECT group_id, UNNEST(ARRAY_AGG(name)) AS elem
FROM t
GROUP BY group_id
) sub
GROUP BY group_id;
这个写法本质是“先聚合成数组 → 展开 → 去重 → 再拼接”,虽多一层,但逻辑清晰、各阶段可调试。
- 如果原始数据量大,
UNNEST可能放大中间结果,注意内存压力 - Trino 中可简化为:
ARRAY_JOIN(ARRAY_DISTINCT(ARRAY_AGG(name)), ',') - BigQuery 不支持
ARRAY_DISTINCT,得用ARRAY(SELECT DISTINCT x FROM UNNEST(arr) AS x)
GROUP BY 字段和去重字段不一致时容易漏数据
常见错误是只按主键分组,却忘了去重字段可能含空值或空字符串——它们会被当作有效值参与拼接,导致结果异常长或含无意义项。
-
NULL在STRING_AGG中默认被忽略(PostgreSQL),但''(空字符串)不会,需提前过滤:WHERE name IS NOT NULL AND name != '' - 大小写敏感问题:'Apple' 和 'apple' 被视为不同值,必要时统一用
LOWER(name)再去重 - 排序影响结果可读性:
STRING_AGG默认无序,建议加ORDER BY子句,如STRING_AGG(DISTINCT name ORDER BY name, ',')
性能差异:STRING_AGG 比 ARRAY_AGG + 转换快得多
纯字符串拼接路径更短。用 ARRAY_AGG 再展开去重,至少多一次哈希分组和数组构造/解构,实测在百万行级别慢 2–3 倍。
除非你需要中间数组做其他操作(比如取前 N 个、判断是否包含某值),否则优先走 STRING_AGG(DISTINCT ...) 直接路线。
- PostgreSQL 15+ 对
STRING_AGG(DISTINCT ...)有优化,可放心用 - MySQL 的
GROUP_CONCAT(DISTINCT ...)在group_concat_max_len限制下可能截断,记得调大 - 如果字段含特殊字符(如逗号、反斜杠),拼接后难解析,建议改用 JSON 数组:
JSON_AGG(DISTINCT name)
实际用哪个,取决于你用的数据库版本和是否需要保留结构化中间态。多数场景下,一条带 DISTINCT 的 STRING_AGG 就够了,别为了“看起来高级”硬套 ARRAY_AGG。










