自 SQL Server 2017 起,新增的 STRING_AGG 聚合函数彻底取代了传统 FOR XML PATH('') 拼接字符串的复杂方式。该函数必须与 GROUP BY 搭配使用,语法简洁明了:STRING_AGG(expression, separator) WITHIN GROUP (ORDER BY ...)。默认情况下,STRING_AGG 会自动忽略 NULL 值,且不保证输出顺序——若要控制顺序,必须显式指定排序子句。

STRING_AGG 函数语法与基本用法详解
STRING_AGG 本质上是一个聚合函数,因此不能在没有 GROUP BY 的情况下单独出现在 SELECT 列表中。最简单的用法是 STRING_AGG(column, ',')。其中第二个参数为分隔符,必须是字符串常量或变量,不能使用列名或表达式——若写成 STRING_AGG(name, separator_col),SQL Server 会抛出错误 Msg 9836, Level 16: The second argument of STRING_AGG must be a constant. 这一点是常见的易错点。
- 分隔符可以设置为空字符串
'',但此时所有结果会直接相连,可读性较差。 - 如果被聚合的列包含 NULL 值,默认情况下会被忽略,不会产生多余的分隔符。
- 返回结果的数据类型取决于输入列,通常为
varchar(max)或nvarchar(max)。
STRING_AGG 中 NULL 值处理与排序控制技巧
STRING_AGG 默认自动跳过 NULL 值,这与 SUM 的行为类似。但如果需要以空字符串占位,可以先用 ISNULL 或 COALESCE 对列值进行预处理。
**排序并非默认行为**,必须显式使用 WITHIN GROUP (ORDER BY ...) 子句。若省略该子句,特别是在并行执行计划中,每次查询结果的顺序可能都不一致。
- 排序字段可以是原表列、列别名,甚至表达式,例如
WITHIN GROUP (ORDER BY LEN(name) DESC)。 - 支持按多字段排序,例如
WITHIN GROUP (ORDER BY status, created_date DESC)。 - 请注意,在
WITHIN GROUP内部不能引用聚合函数(例如AVG(price)是不允许的)。
实际应用示例:STRING_AGG(ISNULL(email, 'N/A'), '; ') WITHIN GROUP (ORDER BY email)。
STRING_AGG 与旧方案 FOR XML PATH 的关键区别
与 FOR XML PATH('') 相比,STRING_AGG 语法更直观、可读性更强,并且内置了排序支持。但它不具备去重功能——如果源数据存在重复值,STRING_AGG 会全部保留;而 FOR XML 方式可以通过子查询配合 DISTINCT 实现去重。
在性能方面,STRING_AGG 在大多数场景下表现更优,特别是处理大量数据分组时,其执行计划更为简洁。但需注意:若分组后的单条聚合结果超过 2GB(varchar(max) 的最大容量),数据会被截断并抛出错误 String or binary data would be truncated。
FOR XML能够自动处理特殊字符(如<会转义为<),而STRING_AGG不会进行任何转义,直接原样拼接。- 不支持嵌套调用:
STRING_AGG(STRING_AGG(...))会导致语法错误。 - 不支持条件聚合(例如将
CASE WHEN ... THEN ... END直接包裹在STRING_AGG中),如有需要,应提前通过 CTE 或子查询进行过滤。
跨版本兼容性及 SQL Server 2016 以下降级方案
如果运行环境为 SQL Server 2016 或更早版本,STRING_AGG 函数不可用,必须采用降级方案。最可靠的替代方法是使用经典的 FOR XML PATH('') 搭配 STUFF 去除首逗号:
STUFF(( SELECT ',' + name FROM users u2 WHERE u2.group_id = u1.group_id FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
但该写法在数据中包含特殊 XML 字符(例如 &、<)时会出错,需要额外使用 REPLACE 或 FOR XML PATH(''), TYPE 配合 .value() 进行处理。
一个容易被忽略的细节:当聚合列为 varchar(50) 且分组结果较长时,FOR XML 写法若未显式转换为 varchar(max),可能被隐式截断至 4000 字符。而 STRING_AGG 默认使用 max 容量,无需担心截断问题。
