在 SQL Server 的实际业务处理中,经常需要将一个由逗号、竖线或其他分隔符拼接而成的长字符串,拆分成多行单独的值,以便关联查询或批量写入。这种操作通常被称为字符串分拆。不同版本的 SQL Server 提供的原生能力差异很大,选择合适的方法直接影响脚本的可读性和执行效率。

一、使用内置 STRING_SPLIT 函数
从 SQL Server 2016(兼容级别 130)开始,系统提供了原生的 STRING_SPLIT 函数。它接收两个参数:待拆分的字符串和分隔符,返回一个只有一列叫 value 的表。由于该函数由数据库引擎底层用 C++ 实现,避免了用户自定义函数中的逐行解释执行开销,在绝大多数场景下性能最佳。
下面的示例演示如何将一个商品编号列表拆开,并与商品表做关联查询:
DECLARE @ids NVARCHAR(200) = '1001,1002,1003,1004'; SELECT p.id, p.name FROM product p INNER JOIN STRING_SPLIT(@ids, ',') AS s ON p.id = CAST(s.value AS INT);
需要注意的是,STRING_SPLIT 的官方文档明确说明:输出行的顺序是不保证的。如果你的业务逻辑强依赖原始字符串中的先后顺序,就不能直接使用它,或者需要配合其它方式标记序号。此外,分隔符仅支持单个字符,不支持多字符分隔串。
对于低于 2016 的实例,这个函数不存在,执行会报无效的对象名错误。此时必须采用兼容旧版本的自定义方案,后文会给出典型写法。
二、自定义表值函数实现分拆
在老版本或需要保持顺序、支持多字符分隔符时,开发者通常编写自定义表值函数(TVF)。最常见的是利用数字辅助表或递归方式循环截取。下面给出一个基于数字表的通用拆分函数示例:
CREATE FUNCTION dbo.fn_SplitWithSeq
(
@str NVARCHAR(MAX),
@sep NVARCHAR(10)
)
RETURNS @t TABLE (seq INT, val NVARCHAR(MAX))
AS
BEGIN
DECLARE @i INT = 1, @pos INT, @len INT = LEN(@sep);
SET @str = @str + @sep;
WHILE CHARINDEX(@sep, @str, @i) > 0
BEGIN
SET @pos = CHARINDEX(@sep, @str, @i);
INSERT INTO @t(seq, val)
VALUES (NULL, SUBSTRING(@str, @i, @pos - @i));
SET @i = @pos + @len;
END
UPDATE @t SET seq = ROW_NUMBER() OVER (ORDER BY (SELECT 1));
RETURN;
END;
这个函数通过 CHARINDEX 不断查找分隔符位置,用 SUBSTRING 截取片段,并借助临时表变量缓存结果。最后用窗口函数补齐序号,从而保留了原始顺序。它的优势在于逻辑透明、可扩展,但缺点也明显:循环处理在超长字符串时会产生大量语句执行,速度远不及内置函数。
如果系统里经常调用此类函数,建议预先建一张物理数字表(如 dbo.Nums 存有 1 到 100000 的整数),改用基于集合的写法代替 WHILE 循环,能显著提升性能。
三、借助 XML 方法进行分拆
另一种在不支持 STRING_SPLIT 的环境里流行的技巧,是把字符串伪装成 XML 再使用 nodes() 方法拆开。该方法纯集合操作,无需循环,兼容性可到 SQL Server 2005。
DECLARE @xml XML;
DECLARE @str NVARCHAR(MAX) = '苹果,香蕉,橘子';
SET @xml = CAST('<r>' + REPLACE(@str, ',', '</r><r>') + '</r>' AS XML);
SELECT t.c.value('.', 'NVARCHAR(50)') AS fruit
FROM @xml.nodes('/r') AS t(c);
上面的代码先将逗号替换成 XML 标签,再强制转换成 XML 类型,最后用 nodes 把每个标签变成一行。这种做法代码紧凑,且能自然处理特殊字符(只要做好转义)。不过当原始字符串里本身包含尖括号或 & 符号时,必须先进行转义替换,否则 CAST 会报错。
从执行计划看,XML 方式会引入 XML 解析器开销,在数据量很大时不如数字表集合解法,但远好于逐行自定义函数。它适合偶尔使用的脚本或中小数据量的报表存储过程。
四、不同方案对比与选型建议
为了直观比较,我们把三种主流做法放在一张表里看差异:
| 方案 | 最低版本 | 是否保序 | 性能表现 | 适用场景 |
|---|---|---|---|---|
| STRING_SPLIT | 2016 | 否 | 最优 | 高并发、大批量、无顺序要求 |
| 自定义TVF | 2005+ | 可保序 | 较差 | 旧版本、需序号、低频调用 |
| XML解析 | 2005+ | 保序 | 中等 | 中小数据、报表脚本 |
在新建项目且数据库版本允许时,优先使用 STRING_SPLIT,并用 OPENJSON 等补充手段解决顺序问题。维护老系统时,推荐用数字表配合基于集合的自定义函数替换 WHILE 循环实现,既保证兼容也兼顾效率。避免在触发器或高频作业中调用逐行截取的函数,那会成为整个库的瓶颈。
字符串分拆虽是小功能,却集中体现了 SQL Server 集合思维与过程思维的差异。理解每种写法的内部机制,才能在正确的地方用正确的语句。
SQL_Server字符串分拆STRING_SPLIT修改时间:2026-08-05 05:48:29