将字符串拆分为行的最有效方法

时间:2014-05-15 06:28:08

标签: sql-server sql-server-2012

我使用以下函数将字符串拆分为行。它比我之前使用的函数快得多,但是我需要以某种方式更快地浏览这些数据(它是一个ETL工作):

ALTER FUNCTION [dbo].[ArrayToTable]
(
     @InputString VARCHAR(MAX) = ''
     , @Delimitter VARCHAR(1) = ',' 
) 
RETURNS @RESULT TABLE([Position] INT IDENTITY, [Value] VARCHAR(MAX)) 
AS 
BEGIN 
    DECLARE @XML XML 
    SELECT @XML = CONVERT(XML, SQL_TEXT) 
    FROM ( 
        SELECT '<root><item>' 
            + REPLACE(@InputString, @Delimitter, '</item><item>') 
            + '</item></root>' AS SQL_TEXT 
        ) dt 

    INSERT INTO @RESULT([Value]) 
    SELECT t.col.query('.').value('.', 'VARCHAR(1000)') AS [Value] 
    FROM @XML.nodes('root/item') t(col) 
    RETURN 
END 

有人能想到更好/更快的方法将分隔的字符串转换成行吗?我在查询中使用cross apply来加入这些结果。

有人能想到一种更有效的方法来将分隔的字符串转换成行吗?

3 个答案:

答案 0 :(得分:6)

这是我所拥有的最高效的功能:

CREATE FUNCTION [Resource].[udf_SplitByXml]
      (@Data NVARCHAR(MAX), @Delimiter NVARCHAR(5))
RETURNS @Table TABLE 
    ( Data NVARCHAR(MAX)
    , SequentialOrder INT IDENTITY(1, 1))
AS
BEGIN

    DECLARE @TextXml XML;
    SELECT @TextXml = CAST('<d>' + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@Data, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '"', '&quot;'), '''', '&apos;'), @Delimiter, '</d><d>') + '</d>' AS XML);

    INSERT INTO @Table (Data)
    SELECT Data = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(RTRIM(LTRIM(T.split.value('.', 'nvarchar(max)'))), '&amp;', '&'), '&lt;', '<'), '&gt;', '>'), '&quot;', '"'), '&apos;', '''')
    FROM @TextXml.nodes('/d') T(Split)

    RETURN
END

以下是可用于测试结果的示例调用:

SELECT * FROM Resource.udf_SplitByXml('yes, no, maybe, so', ',');
SELECT * FROM Resource.udf_SplitByXml('who|what|where|when|why|how|Uh, I don''t know!', '|');
SELECT * FROM Resource.udf_SplitByXml('Government, Education, Non-profit|Energy & Power|Yes|No', '|');
SELECT * FROM Resource.udf_SplitByXml('Energy & Power|Some<Thing>Wicked''This"Way Comes', '|');

另一个选择是尝试基于Adam Machanic代码的CLR解决方案,该代码是性能测试in this blog的赢家。

答案 1 :(得分:0)

这是我刚才写的另一个函数,令我惊讶的是,它比上面的XML方法快一点,尽管只是勉强。 比较两个函数来处理1,000行分隔的字符串不会产生任何性能差异。处理50K行分隔字符串,XML方法需要129秒才能生成435,217个提取行,相比之下,为我的简单字符串操作方法生成提取的435,217行需要122秒。

所以它并没有真正快得多,但我想如果你处理数十万行会有所不同。我的功能的主要好处是它易于阅读和理解,它不依赖于可能在SQL Server的未来版本中更改的XML功能,并且它应该可以轻松移植到任何语言。我真的希望找到一些非常快的东西,但我想这是我们现在能做的最好的事情。

  CREATE FUNCTION dbo.ufn_util_Split
     (
     @RawText varchar(max),
     @SplitCharacter varchar(2)
     )
  RETURNS @t_Results TABLE
     (
     RowIndex int IDENTITY(1,1),
     RowValue varchar(max)
     )

  AS

  BEGIN
     DECLARE @vc_RowValue varchar(max) = ''
     DECLARE @vc_Remainder varchar(max) = CASE WHEN RIGHT(@RawText,1) = @SplitCharacter THEN @RawText ELSE @RawText + @SplitCharacter END   --the string must end in the split character in order for this to work
     DECLARE @int_SplitPosition int = 0
     DECLARE @int_LenSplitChar int = 0
     SELECT @int_LenSplitChar = LEN(@SplitCharacter)

     --determine the first segment to start with
     SELECT @int_SplitPosition = CHARINDEX(@SplitCharacter,@vc_Remainder)
     WHILE (@int_SplitPosition > 0)
     BEGIN
        SELECT @vc_RowValue = LEFT(@vc_Remainder,@int_SplitPosition-1)
        INSERT INTO @t_Results (RowValue) VALUES (@vc_RowValue)

        --now strip off the segment we just extracted and determine where the next segment ends, and continue
        SET @vc_Remainder = SUBSTRING(@vc_Remainder,@int_SplitPosition+@int_LenSplitChar,LEN(@vc_Remainder))
        SELECT @int_SplitPosition = CHARINDEX(@SplitCharacter,@vc_Remainder)
        CONTINUE
     END
     RETURN
  END

答案 2 :(得分:0)

嗨,试试这个 -

create procedure sp_getAllItems 
@input varchar(100)
as
BEGIN

    create table #tmpFruits (name varchar(10))

    Declare @Qry Varchar(500)      
    Set @Qry = ''        
    Select @Qry = @Qry + ' Insert into #tmpFruits '    
    Select @Qry = @Qry +  Replace( 'Select ''' + 
    Replace(Replace(Replace(Replace(@input,CHAR(9),''),' 
    ',''),CHAR(10),''),CHAR(13),'') , ',',''' Union Select ''') + ''''           

    Exec (@Qry) 

    select * from Fruitstest where name in (select name from #tmpFruits)
    drop table #tmpFruits 

END

exec sp_getAllItems @input = 'cherry,banana'