只有SQL Server 2017+支持STRING_AGG,低版本需用FOR XML PATH('')+STUFF;必须配合GROUP BY使用,支持WITHIN GROUP排序、DISTINCT去重及NULL处理,分隔符不可为NULL。

只有 SQL Server 2017(含)及以上版本才能直接用 STRING_AGG 实现分组后多行合并;低版本会报错 Invalid object name 'STRING_AGG',必须换方案。
确认版本再动手:SELECT @@VERSION 是第一步
不查版本就写 STRING_AGG,大概率执行失败。常见错误不是语法写错,而是实例是 SQL Server 2016 或更早——这个函数根本不存在。Azure SQL Database 一般支持,但旧版托管实例可能兼容级别低于 140,也得检查:
- 运行
SELECT @@VERSION看主版本号,≥ 14.0 表示 2017+ - 若用 Azure SQL,补查
SELECT DATABASEPROPERTYEX(DB_NAME(), 'CompatibilityLevel'),结果需 ≥ 140 - 版本不够?别折腾“启用”,直接转向
FOR XML PATH('')+STUFF方案
STRING_AGG 必须和 GROUP BY 配合,不能单独用
在无分组的查询里写 STRING_AGG(name, ', '),SQL Server 会报错:STRING_AGG is not allowed in the context。它本质是聚合函数,行为和 SUM、COUNT 一致,必须明确作用域。
- 正确结构:先
GROUP BY dept_id,再在SELECT中用STRING_AGG(emp_name, '; ') - 如果只是想把全表所有行拼成一行,也得加个无意义分组,比如
GROUP BY ()(SQL Server 支持空括号分组)或GROUP BY 1(配合常量) - JOIN 后使用要小心:LEFT JOIN 产生 NULL 行时,
STRING_AGG返回NULL;而FOR XML方案可能返回空字符串,逻辑不一致
排序和 NULL 处理不是可选项,而是易出错点
不加 WITHIN GROUP (ORDER BY ...),拼接顺序完全不可控——即使源表有聚集索引或 ORDER BY,也不影响 STRING_AGG 输出顺序。NULL 值则默认被跳过,不会留空位。
- 强制排序:哪怕只按主键排,也要写
STRING_AGG(name, ', ') WITHIN GROUP (ORDER BY id) - 分隔符不能传
NULL:写STRING_AGG(name, NULL)整组结果变NULL;想连起来就用STRING_AGG(name, '') - 处理 NULL 字段:用
ISNULL(email, '')或COALESCE(email, '')转占位符,否则该值彻底消失 - 去重需求:直接加
DISTINCT,如STRING_AGG(DISTINCT tag, ', '),但注意WITHIN GROUP的ORDER BY字段必须出现在DISTINCT表达式中
低版本替代方案:FOR XML PATH('') 不只是“能用”,更要防转义
SQL Server 2016 及以前只能靠 FOR XML 模拟。但直接拼接容易把 &、、> 自动转成 &、、>,数据就错了。
- 必须加
TYPE和.value('.', 'NVARCHAR(MAX)'):STUFF((SELECT ',' + ISNULL(name, '') FROM t FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') - 别用
CAST(... AS XML)简写,它不触发实体解码 -
STUFF比SUBSTRING更安全:空结果集时不会报错 - 性能差异明显:大数据量下比
STRING_AGG慢 20%–40%,且无法并行优化
真正麻烦的不是语法本身,而是跨版本逻辑一致性——比如 LEFT JOIN 后空子集的聚合结果是 NULL 还是空字符串,STRING_AGG 和 FOR XML 行为不同,业务代码得兜底判断。










