mysql 8.0+ 直接用 group_concat(distinct ...) 实现去重拼接,需注意默认排序、null跳过、长度限制及先拆分再聚合的场景;postgresql 需 string_to_array + unnest + distinct + string_agg;sql server 2017+ 推荐 string_split + 子查询去重 + string_agg。

MySQL 8.0+ 用 GROUP_CONCAT(DISTINCT ...) 最直接
如果你用的是 MySQL 8.0 或更新版本,GROUP_CONCAT 支持 DISTINCT 关键字,能天然解决“先去重、再拼接”的需求。注意它默认按字段值排序,且对 NULL 值静默跳过。
常见错误是漏写 DISTINCT 或误以为它会自动拆分字符串——它只对聚合前的行去重,不处理单个字段内部的逗号分隔内容。
- 假设表
t有字段tags(值如"a,b,c"、"b,d"),但你想按某组(如category)聚合所有出现过的独立 tag,就得先UNION拆开,再GROUP_CONCAT(DISTINCT tag) -
GROUP_CONCAT默认最大长度是 1024 字符,超长会被截断;需提前设SET SESSION group_concat_max_len = 1000000; - 排序可控:
GROUP_CONCAT(DISTINCT tag ORDER BY tag ASC SEPARATOR ',')
PostgreSQL 用 STRING_AGG(DISTINCT ...) + UNNEST(STRING_TO_ARRAY(...))
PostgreSQL 不支持在 STRING_AGG 内直接写 DISTINCT 修饰子查询结果,必须显式展开再去重。核心链路是:字符串 → 数组 → 展开为行 → 去重 → 聚合。
容易踩的坑是忽略 NULL 处理和空字符串干扰。比如 STRING_TO_ARRAY('a,,c', ',') 会生成 {'a', '', 'c'},那个空元素可能被误认为有效 tag。
- 安全写法:用
NULLIF(trim(elem), '')过滤空/空白项 - 完整示例:
SELECT category, STRING_AGG(DISTINCT elem, ',') FROM t, UNNEST(STRING_TO_ARRAY(tags, ',')) AS elem WHERE elem IS NOT NULL AND trim(elem) != '' GROUP BY category;
- 性能提示:大量数据时,
UNNEST+GROUP BY可能比 MySQL 的GROUP_CONCAT(DISTINCT)更耗资源
SQL Server 需要 STRING_SPLIT + FOR XML 或 STRING_AGG(2017+)
SQL Server 2016 引入 STRING_SPLIT,但它返回的是无序表,且不保证去重;2017+ 才有 STRING_AGG。所以老版本只能靠 FOR XML PATH('') 拼接,新版本推荐组合使用。
关键陷阱:默认 STRING_SPLIT 不处理重复值,也不过滤空字符串;且 STRING_AGG 不支持直接 DISTINCT,仍需子查询或 CTE 去重。
- SQL Server 2017+ 推荐写法:
SELECT category, STRING_AGG(tag, ',') FROM ( SELECT DISTINCT category, TRIM(value) AS tag FROM t CROSS APPLY STRING_SPLIT(tags, ',') WHERE TRIM(value) != '' ) AS dedup GROUP BY category;
- 旧版兼容方案里,
FOR XML的TYPE和.value()调用容易出错,尤其含特殊字符(如&、)时需额外转义
通用思路:别试图在单条 SQL 里“智能解析”嵌套结构
所有方案都基于一个前提:你明确知道字段中逗号是分隔符,且不含转义或嵌套(比如 "a,\"b,c\",d")。一旦出现这种复杂格式,SQL 就不是合适工具——正则解析、CSV 解析逻辑应交给应用层。
另一个常被忽略的点是字符集与排序规则影响去重结果。比如 'café' 和 'cafe' 在某些 collation 下可能被判定为相同,导致意外合并。
真正麻烦的从来不是拼接,而是确认“哪些值算重复”。这取决于业务定义,不是数据库能自动猜出来的。











