由于&符号,SQL Server 2008拆分字符串失败

时间:2018-02-15 16:08:12

标签: sql-server tsql

我创建了一个存储过程来尝试复制现在在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

它确实有效,但如果文本字符串的任何部分包含&符号[&amp;],它就不会拆分文本字符串。

我找到了数百个拆分字符串的例子,但似乎都没有处理特殊字符。

所以使用这个:

select * 
from MySplit('Test1,Test2,Test3', ',') 

工作正常,但

select * 
from MySplit('Test1 & Test4,Test2,Test3', ',') 

没有。它失败了

  

XML解析:第1行,第17个字符,非法名称字符。

我做错了什么?

更新

首先,感谢@marcs,向我展示了我写这个问题的方式的错误。

其次,感谢下面的所有帮助,特别是@PanagiotisKanavos和@MatBailie

由于这是将数据从旧系统迁移到新系统的丢弃代码,我选择使用@MatBailie解决方案,快速而且非常脏,但也非常适合这项任务。

但是,在未来,我将继续推进@PanagiotisKanavos解决方案。

3 个答案:

答案 0 :(得分:2)

修改您的功能并将所有&替换为&amp; 这将删除错误。发生这种情况是因为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,'&','&amp;') 
  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不认为这是一个普通的分裂器。

您可以按照编码值替换无效字符,例如&&amp; ,但您必须确定您的文字从不包含这样的编码。

也许您应该研究不同的技术,比如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;