SQLite group_concat 去重并指定分隔符时报单参数错误
SQLite 的 group_concat 同时使用 DISTINCT 和自定义分隔符时报错。先按分组键和值去重,再执行双参数聚合,避免替换结果中的逗号破坏原始内容。
SQLite 把去重和自定义分隔符放进同一次 group_concat 调用时,可能直接拒绝查询:
SELECT group_concat(DISTINCT name, '|') FROM labels;
DISTINCT aggregates must have exactly one argument
这里不是分隔符字符非法,也不是结果超过长度限制。SQLite 的聚合 DISTINCT 语法要求单个参数,而 group_concat(name, '|') 使用了两个参数。需要同时去重和指定分隔符时,可以先在子查询中生成去重后的行,再交给外层聚合。
本文针对普通聚合查询。带 OVER 的窗口函数有自己的限制,不能把下面的写法直接挪到窗口表达式中。
按分组键和值一起去重
如果要为每个项目拼接不同的标签,子查询必须保留项目编号。下面假设 labels(project_id, name) 是已有表:
SELECT project_id, group_concat(name, '|') AS names
FROM (
SELECT DISTINCT project_id, name
FROM labels
) AS distinct_labels
GROUP BY project_id;
内层按 (project_id, name) 去除重复行,外层再为每个项目拼接标签。这样同一个标签可以分别出现在不同项目的结果中,不会因为全表去重而丢掉所属项目。
不要把内层改成 SELECT name FROM labels GROUP BY project_id。那会让每个项目只留下某一行的 name,外层已经拿不到这个项目的全部标签。原查询有租户过滤、关联或权限范围时,也要保留;示例中的 labels 只是输入关系的占位,不是让业务查询取消筛选。
如果确实只需要全表一份去重列表,可以使用:
SELECT group_concat(name, '|') AS names
FROM (SELECT DISTINCT name FROM labels) AS distinct_labels;
去重仍受值的类型和文本排序规则影响。不要为了让大小写不同的标签合并,顺手加一个 lower();那会改变输出内容和去重含义,应先确定业务要求。
不要替换拼接结果里的全部逗号
另一种看似更短的写法是先用默认分隔符,再把逗号换成竖线:
SELECT replace(group_concat(DISTINCT name), ',', '|') FROM labels;
如果标签本身是 Smith, Jr.,这条语句也会改掉标签里的逗号。它无法区分“原始内容”和“聚合添加的分隔符”。追加特殊标记后再替换、裁剪,也需要证明标记不会出现在数据中;通常不如明确的去重子查询容易维护。
即使采用正确的两个阶段,标签本身含 | 时,结果也不能直接按竖线可靠地拆回原来的标签。这里生成的是展示字符串,不是自动带转义规则的序列化格式。需要无损传输时应保留多行结果,或另行采用双方明确支持的结构化格式。
需要固定顺序时,单独约定排序
前面的查询解决参数报错和去重,没有承诺拼接顺序。外层 ORDER BY project_id 只能排列项目行,不能决定每个项目内部的标签顺序。
SQLite 3.44.0 起支持在聚合函数最后一个参数之后写 ORDER BY。如果运行库满足这一条件,可以写:
SELECT project_id,
group_concat(name, '|' ORDER BY name COLLATE BINARY) AS names
FROM (
SELECT DISTINCT project_id, name
FROM labels
) AS distinct_labels
GROUP BY project_id
ORDER BY project_id;
这里按 BINARY 规则排列标签;它不等于中文拼音或本地化排序。去重与排序是两件事,这段代码没有把内层 DISTINCT 的比较规则一并改成 BINARY。如果原字段使用其他排序规则,应确认两者是否符合预期。
在应用实际使用的连接中,用 SELECT sqlite_version(); 核对版本。系统命令行工具与应用内嵌库可能不同;旧版不要直接使用这段聚合内排序语法,也不要用一次碰巧有序的输出作为顺序保证。
用重复值和包含分隔符的数据检查结果
先保存原查询,在隔离数据中核对同项目重复标签、不同项目同名标签、带逗号的名称和 NULL。group_concat 不拼接 NULL;空字符串则是一个值,不应随意当作空值删除。
修复后应确认查询不再出现单参数报错,各项目保留了完整的不同标签,名称内的逗号没有被改写。如果约定了固定顺序,再检查排序后的实际字符串,不仅检查行数或查询能否执行。
这项修改只调整查询结构,不需要删除重复数据、修改表结构或重建数据库。上线前评估额外去重步骤对真实数据量的执行成本;结果或性能不符合预期时,回退应用中的查询变更,保留原始数据继续排查。


