postgresql 10+ 支持 array_agg(distinct tag),性能好、语义清晰,但需注意 null 排序、空格处理及显式 order by;其他数据库需预去重子查询,mysql 仅 group_concat(distinct) 特例支持。

直接用 ARRAY_AGG(DISTINCT ...),但必须确认数据库是 PostgreSQL 10+;其他数据库不支持该语法,硬套会报错。
PostgreSQL 10+:用 ARRAY_AGG(DISTINCT) 最省事
这是唯一原生支持在聚合函数内直接去重的主流方案。它不依赖子查询或临时展开,性能好、语义清晰。
-
ARRAY_AGG(DISTINCT tag)返回数组,后续可直接用array_to_string()、array_length()或下标取值(如(arr)[1]) - 若字段含
NULL,默认排序把NULL排最前——业务上常要ORDER BY tag NULLS LAST - 若字符串带首尾空格(如
'VIP '和'VIP'),需先trim(tag)再DISTINCT,否则算两个不同值 - 不写
ORDER BY时结果顺序不确定,哪怕源表有序也不保序
MySQL / SQL Server / Oracle:没有 DISTINCT inside STRING_AGG,得绕路
这些数据库的 STRING_AGG(或等效函数如 GROUP_CONCAT)不支持内置 DISTINCT 参数。强行写 STRING_AGG(DISTINCT tag, ',') 会直接报错 ERROR: syntax error at or near "DISTINCT"。
- MySQL 可用
GROUP_CONCAT(DISTINCT tag)—— 这是特例,但返回字符串,无法再取长度或单个元素 - SQL Server 要用
STRING_AGG+ CTE 预去重:SELECT STRING_AGG(tag, ',') FROM (SELECT DISTINCT tag FROM t WHERE ...) AS dedup - Oracle 用
LISTAGG也得先DISTINCT子查询,否则重复项照拼 - 所有绕路方案都多一次中间结果物化,大数据量时 I/O 和内存开销明显上升
去重逻辑复杂时(比如按优先级选值),别硬塞 DISTINCT
当“去重”实际是“按规则选一条”(例如:同一 user_id 下,优先取 source = 'system' 的 tag,没有才 fallback 到 'manual'),DISTINCT 完全无能为力。
- 正确做法是把逻辑推到输入层:用
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY CASE source WHEN 'system' THEN 1 ELSE 2 END)标记优先级 - 再对外层结果调用
ARRAY_AGG(tag)或STRING_AGG(tag),此时输入已干净,无需DISTINCT - 硬在
ARRAY_AGG里套UNNEST+DISTINCT+ARRAY是典型反模式:嵌套深、难调试、执行计划差
真正容易被忽略的是:去重拼接不是纯语法问题,而是数据语义问题——NULL 怎么排、空格算不算差异、重复到底是值重复还是业务逻辑重复。选函数之前,先厘清“谁才算重复”。











