我创建了一个存储过程来尝试复制现在在SQL Server 2016中的split_string
函数。
到目前为止,我已经得到了这个:
CREATE FUNCTION MySplit
(@delimited NVARCHAR(MAX), @delimiter NVARCHAR(100))
RETURNS @t TABLE
(
-- Id column can be commented out, not required for SQL splitting string
id INT IDENTITY(1,1), -- I use this column for numbering split parts
val NVARCHAR(MAX)
)
AS
BEGIN
DECLARE @xml XML
SET @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'
INSERT INTO @t(val)
SELECT
r.value('.','varchar(max)') AS item
FROM
@xml.nodes('//root/r') AS records(r)
RETURN
END
GO
它确实有效,但如果文本字符串的任何部分包含&符号[&
],它就不会拆分文本字符串。
我找到了数百个拆分字符串的例子,但似乎都没有处理特殊字符。
所以使用这个:
select *
from MySplit('Test1,Test2,Test3', ',')
工作正常,但
select *
from MySplit('Test1 & Test4,Test2,Test3', ',')
没有。它失败了
XML解析:第1行,第17个字符,非法名称字符。
我做错了什么?
更新
首先,感谢@marcs,向我展示了我写这个问题的方式的错误。
其次,感谢下面的所有帮助,特别是@PanagiotisKanavos和@MatBailie
由于这是将数据从旧系统迁移到新系统的丢弃代码,我选择使用@MatBailie解决方案,快速而且非常脏,但也非常适合这项任务。
但是,在未来,我将继续推进@PanagiotisKanavos解决方案。
答案 0 :(得分:2)
修改您的功能并将所有&
替换为&
这将删除错误。发生这种情况是因为XML无法解析&
,因为它是内置标记。
答案 1 :(得分:1)
Create FUNCTION [dbo].[split_stringss](
@delimited NVARCHAR(MAX),
@delimiter NVARCHAR(100)
) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE @xml XML
DECLARE @var NVARCHAR(MAX)
DECLARE @var1 NVARCHAR(MAX)
set @var1 = Replace(@delimited,'&','&')
SET @xml = N'<t>' + REPLACE(@var1,@delimiter,'</t><t>') + '</t>'
INSERT INTO @t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM @xml.nodes('/t') as records(r)
RETURN
END
答案 2 :(得分:0)
首先,SQL Server 2016引入了STRING_SPLIT TVF。你可以写CROSS APPLY STRING_SPLIT(thatField,',') as items
在以前的版本中,您仍然需要创建自定义拆分功能。 There are various techniques。最快的解决方案是使用SQLCLR功能。
在一些案例中,第二快的是您使用的 - 将文本转换为XML并选择节点。正如您所发现的,这种分裂技术的一个众所周知的问题是非法的XML字符会破坏它。这就是为什么Aaron Bertrand不认为这是一个普通的分裂器。
您可以按照编码值替换无效字符,例如&
与&
,但您必须确定您的文字从不包含这样的编码。
也许您应该研究不同的技术,比如Moden功能,在许多情况下可以更快:
CREATE FUNCTION dbo.SplitStrings_Moden
(
@List NVARCHAR(MAX),
@Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS ( SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E42(N) AS (SELECT 1 FROM E4 a, E2 b),
cteTally(N) AS (SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1)))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0))
SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000))
FROM cteStart s;
我个人创建并使用了SQLCLR UDF。
另一种选择是避免完全拆分并将表值参数从客户端传递到服务器。或者使用像Dapper这样的microORM,可以从值列表中构造IN (...)
子句,例如:
var products=connection.Query<Product>("select * from products where id in @ids",new {ids=myIdArray});
支持LINQ的类似EF的ORM也可以生成IN
子句:
var products = from product in dbContext.Products
where myIdArray.Contains(product.Id)
select product;