string_split在sql server 2016+中需满足三条件:版本≥2016且兼容级≥130;必须用cross/outer apply调用;参数为单字符分隔符。返回value列无序、含空值,需where过滤并慎用in子查询。

SQL Server 2016+ 直接用 STRING_SPLIT,别写自定义函数或 XML;低版本必须手动拆时,优先用递归 CTE 而非 WHILE 循环——后者在集合操作中性能差、难调试。
STRING_SPLIT 在 SQL Server 中怎么用才不出错
STRING_SPLIT 看似简单,但实际踩坑点集中在三处:参数类型、结果无序、空值处理。
-
string和separator必须是单字符;传入','可以,但', '(逗号+空格)会报错或拆错 - 返回结果默认无顺序保证,
ORDER BY value不等于原始位置顺序;需要序号得加enable_ordinal = 1(仅 SQL Server 2022+ 或 Azure) - 输入字符串末尾带逗号(如
'a,b,c,')会多出一个空字符串行;WHERE value != ''可过滤,但注意value是nvarchar类型,不能用IS NOT NULL判空 - 不支持直接 JOIN 原表字段做多值匹配(如
WHERE col IN (SELECT value FROM STRING_SPLIT(...))),因为子查询无法关联外层;要用CROSS APPLY
正确写法示例:
SELECT t.ID, s.value FROM Orders t CROSS APPLY STRING_SPLIT(t.ProductIDs, ',') s WHERE LTRIM(RTRIM(s.value)) != '';
SQL Server 2014 及更早版本怎么安全拆分
不能用 STRING_SPLIT,XML 方案看似简洁,但对特殊字符(、<code>&、'')会解析失败;递归 CTE 是更可控的选择。
- 必须给递归设置
OPTION (MAXRECURSION n),否则默认只跑 100 层,长字符串直接中断 - 每次递归要截掉已处理部分,推荐用
SUBSTRING+CHARINDEX组合,且在CHARINDEX的第三个参数显式指定起始位置,避免重复匹配 - 原始字符串末尾不加哨兵字符(如逗号)也行,但需在递归终止条件里补全判断:
CHARINDEX(',', Remaining) = 0 - 记得用
ISNULL或NULLIF处理空片段,否则''和NULL混在一起难区分
最小可用递归模板:
WITH Split AS (
SELECT
CAST(LEFT(@str, CHARINDEX(',', @str + ',') - 1) AS NVARCHAR(MAX)) AS value,
STUFF(@str, 1, CHARINDEX(',', @str + ','), '') AS remaining
UNION ALL
SELECT
CAST(LEFT(remaining, CHARINDEX(',', remaining + ',') - 1) AS NVARCHAR(MAX)),
STUFF(remaining, 1, CHARINDEX(',', remaining + ','), '')
FROM Split
WHERE remaining != ''
)
SELECT value FROM Split OPTION (MAXRECURSION 0);
为什么不该在 WHERE 里用 STRING_SPLIT 做 IN 匹配
常见错误写法:WHERE ProductID IN (SELECT value FROM STRING_SPLIT(@ids, ','))。这不是语法错误,但逻辑危险。
- 如果
@ids是空字符串或NULL,STRING_SPLIT返回空集,整个IN判断变成WHERE ... IN ()→ 永远不成立,查不到任何数据,且不报错 - 如果
@ids含非法字符(如 Unicode 分隔符),STRING_SPLIT可能静默跳过或截断,结果漏数据 - 执行计划里,SQL Server 很可能把子查询当成“非相关子查询”,无法利用索引;而
CROSS APPLY能触发嵌套循环优化 - 真正需要“某字段值是否在逗号串中”时,应该反向思考:把逗号串转成表,再和主表
EXISTS关联
安全替代写法:
SELECT * FROM Products p WHERE EXISTS ( SELECT 1 FROM STRING_SPLIT(@target_ids, ',') s WHERE p.ProductID = s.value );
最易被忽略的点:所有拆分方案都默认把连续逗号('a,,c')视作含空元素,但业务上往往要跳过空值;STRING_SPLIT 不提供过滤开关,必须靠外层 WHERE 显式剔除,且这个 WHERE 要放在 CROSS APPLY 之后、不能提前下推到函数内部。










