sql server 2017中,string_agg用于多行合并(需group by、within group排序、显式处理null),string_split用于字符串拆分(返回单列表值,不保证顺序,不支持多字符分隔符);二者不可互换或直接嵌套,且均受set concat_null_yields_null影响。

SQL Server 2017 中字符串拆分用 STRING_SPLIT,合并用 STRING_AGG;但两者在存储过程中不能直接互换位置或嵌套调用,必须注意上下文和 NULL 行为。
STRING_AGG 在存储过程中必须显式处理排序和 NULL
在存储过程里写 STRING_AGG(col, ',') 得到的结果顺序不可靠,且只要 col 是 NULL,整行就从聚合中消失(不是留空,是彻底跳过)。
- 强制排序只能写在函数内部:
STRING_AGG(col, ',') WITHIN GROUP (ORDER BY id DESC),外部ORDER BY无效 - NULL 值必须提前转换:
STRING_AGG(ISNULL(col, '(missing)'), ',')或STRING_AGG(COALESCE(col, ''), ',') - 分隔符不能为
NULL,空字符串要写成SEPARATOR ''(SQL Server 2022+ 支持,2017 不支持该语法,得用''字面量) - 不支持
DISTINCT,要去重得先套一层 CTE 或子查询:SELECT STRING_AGG(val, ',') FROM (SELECT DISTINCT val FROM @t) t
STRING_SPLIT 返回表值,不能直接用于 INSERT … VALUES
STRING_SPLIT 返回的是单列结果集(value),在存储过程中若想把拆出来的值插入目标表,不能写成 INSERT INTO t(col) VALUES (SELECT value FROM STRING_SPLIT(@s, ',')) —— 这会报错“子查询返回多行”。
- 正确做法是用
INSERT ... SELECT:INSERT INTO t(col) SELECT value FROM STRING_SPLIT(@s, ',') - 拆分后想保留原始顺序?
STRING_SPLIT不保证顺序,SQL Server 2022+ 才加了ordinal列;2017 必须自己加编号,比如用ROW_NUMBER() OVER (ORDER BY (SELECT 0))配合临时表 - 分隔符是特殊字符(如
;、|)没问题,但不能是空字符串或NULL,否则返回空结果集 - 输入字符串超长(> 8000 字节)时,
NVARCHAR(MAX)参数能接住,但拆分性能会明显下降,建议前端先做长度校验
拆分后需要逐行处理?别用游标,改用集合操作
常见误区:在存储过程中对 STRING_SPLIT 结果集开游标,一行行取值再执行 UPDATE/INSERT。这既慢又难维护。
- 99% 场景可用集合操作替代:比如“按逗号字符串更新用户状态”,直接
UPDATE u SET status = 'active' FROM users u INNER JOIN STRING_SPLIT(@ids, ',') s ON u.id = s.value - 如果真要逐行逻辑(如调用另一个存储过程),优先用临时表 +
WHILE循环,比游标快 3–5 倍,且更易调试 -
STRING_SPLIT不过滤空字符串,'a,,b'会返回三行:'a'、''、'b';需要加WHERE value != ''或NULLIF(value, '') IS NOT NULL
兼容旧版本的兜底方案仍需保留
即使你用的是 SQL Server 2017,也不能完全丢掉 FOR XML PATH 和自定义拆分函数——因为某些场景下它们仍是唯一解。
-
STRING_AGG不支持条件聚合(如“只合并 price > 100 的记录”),此时还得用FOR XML PATH+STUFF -
STRING_SPLIT不支持多字符分隔符(如'|||'),遇到就得回退到自定义函数或CHARINDEX+SUBSTRING循环 - 跨版本部署时,用
SERVERPROPERTY('ProductVersion')动态判断,避免硬切语法导致上线失败
最常被忽略的一点:所有这些函数在存储过程中都受 SET CONCAT_NULL_YIELDS_NULL 影响。如果它被设为 OFF(老系统常见),STRING_AGG(NULL, ',') 可能返回 NULL 而非跳过,行为和文档不一致。上线前务必检查这个选项。











