How Do I Split a Delimited String in SQL Server Without Creating a Function?

前端 未结 11 1696
慢半拍i
慢半拍i 2020-11-29 08:28

I\'m working with a SQL Server database. I have a column which contains a delimited list, and I need to write a query which splits the values of the list into rows. From bro

相关标签:
11条回答
  • 2020-11-29 09:00

    A version using XML.

    declare @S varchar(100) = 'Hello John Smith'
    
    select 
      n.r.value('.', 'varchar(50)')
    from (select cast('<r>'+replace(@S, ' ', '</r><r>')+'</r>' as xml)) as s(XMLCol)
      cross apply s.XMLCol.nodes('r') as n(r)
    

    Using a table instead Replace @T with what ever table you are using.

    -- Test table
    declare @T table (ID int, Col varchar(100))
    insert into @T values (1, 'Hello John Smith')
    insert into @T values (2, 'xxx yyy zzz')
    
    select 
      T.ID,
      n.r.value('.', 'varchar(50)')
    from @T as T
      cross apply (select cast('<r>'+replace(replace(Col,'&','&amp;'), ' ', '</r><r>')+'</r>' as xml)) as S(XMLCol)
      cross apply S.XMLCol.nodes('r') as n(r)
    

    Splitting the string 'Hello John Smith' without using a variable

    select 
      n.r.value('.', 'varchar(50)')
    from (select cast('<r>'+replace('Hello John Smith', ' ', '</r><r>')+'</r>' as xml)) as s(XMLCol)
      cross apply s.XMLCol.nodes('r') as n(r)
    
    0 讨论(0)
  • 2020-11-29 09:01

    For late comers to this question, the article at http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings provides an excellent analysis of performance for various options. Some of the options considered include (copied from the site for reference):

    CLR

    .Net code at http://dataeducation.com/sqlclr-string-splitting-part-2-even-faster-even-more-scalable/.

    CREATE ASSEMBLY CLRUtilities FROM 'c:\DLLs\CLRUtilities.dll' 
      WITH PERMISSION_SET = SAFE;
    GO
    
    CREATE FUNCTION dbo.SplitStrings_CLR
    (
       @List      NVARCHAR(MAX),
       @Delimiter NVARCHAR(255)
    )
    RETURNS TABLE ( Item NVARCHAR(4000) )
    EXTERNAL NAME CLRUtilities.UserDefinedFunctions.SplitString_Multi;
    GO
    

    XML

    CREATE FUNCTION dbo.SplitStrings_XML
    (
       @List       NVARCHAR(MAX),
       @Delimiter  NVARCHAR(255)
    )
    RETURNS TABLE
    WITH SCHEMABINDING
    AS
       RETURN 
       (  
          SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
          FROM 
          ( 
            SELECT x = CONVERT(XML, '<i>' 
              + REPLACE(@List, @Delimiter, '</i><i>') 
              + '</i>').query('.')
          ) AS a CROSS APPLY x.nodes('i') AS y(i)
       );
    GO
    

    CTE

    CREATE FUNCTION dbo.SplitStrings_CTE
    (
       @List       NVARCHAR(MAX),
       @Delimiter  NVARCHAR(255)
    )
    RETURNS @Items TABLE (Item NVARCHAR(4000))
    WITH SCHEMABINDING
    AS
    BEGIN
       DECLARE @ll INT = LEN(@List) + 1, @ld INT = LEN(@Delimiter);
    
       WITH a AS
       (
           SELECT
               [start] = 1,
               [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                           @List, @ld), 0), @ll),
               [value] = SUBSTRING(@List, 1, 
                         COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                           @List, @ld), 0), @ll) - 1)
           UNION ALL
           SELECT
               [start] = CONVERT(INT, [end]) + @ld,
               [end]   = COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                           @List, [end] + @ld), 0), @ll),
               [value] = SUBSTRING(@List, [end] + @ld, 
                         COALESCE(NULLIF(CHARINDEX(@Delimiter, 
                           @List, [end] + @ld), 0), @ll)-[end]-@ld)
           FROM a
           WHERE [end] < @ll
       )
       INSERT @Items SELECT [value]
       FROM a
       WHERE LEN([value]) > 0
       OPTION (MAXRECURSION 0);
    
       RETURN;
    END
    GO
    

    Function

    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;
    

    It turns out, the best performance comes from using the CLR function while the XML solution also does well. In nearly all cases, using a numbers table (the approach was not duplicated above) lead to the worst performance.

    0 讨论(0)
  • 2020-11-29 09:01

    Have you looked at the numbers table?

    http://www.sqlservercentral.com/articles/T-SQL/62867/

    http://bradsruminations.blogspot.com/2010/08/integer-list-splitting-sql-fable.html

    0 讨论(0)
  • 2020-11-29 09:04
     DECLARE @cols  AS NVARCHAR(max), 
            @Val   VARCHAR(100)='Hi- Hello break this-Wall', 
            @Deli  VARCHAR(50)='-', 
            @query AS NVARCHAR(max) 
    
    SELECT @cols = Stuff((SELECT ',' + Quotename(id) 
                          FROM   (SELECT stringpieceid AS ID, 
                                         stringpiece 
                                  FROM 
                         [Utility].[dbo].[Splitstringtotable](@Val, @Deli)) 
                                 X 
                          FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, 
                   '') 
    
    SELECT @query = 
    'SELECT * FROM (SELECT StringPieceID as ID,StringPiece from [Utility].[dbo].[SplitStringToTable](''' 
             + @Val + ''',''' + @Deli + '''))X PIVOT  (     MAX(StringPiece)     for [ID] in (' + @cols 
             + ') ) P' 
    
    PRINT @query 
    
    EXEC Sp_executesql 
      @query 
    
    0 讨论(0)
  • 2020-11-29 09:09
    USE TRIAL
    GO
    
    CREATE TABLE DETAILS
    (
    ID INT,
    NAME VARCHAR(50),
    ADDRESS VARCHAR(50)
    )
    
    INSERT INTO DETAILS
    VALUES (100, 'POPE-JOHN-PAUL','VATICAN CIT|ROME|ITALY')
    ,(240, 'SIR-PAUL-McARTNEY','NEWYORK CITY|NEWYORK|USA')
    ,(460,'BARRACK-HUSSEIN-OBAMA','WHITE HOUSE|WASHINGTON|USA')
    ,(700, 'PRESIDENT-VLADAMIR-PUTIN','RED SQUARE|MOSCOW|RUSSIA')
    ,(950, 'NARENDRA-DAMODARDAS-MODI','10 JANPATH|NEW DELHI|INDIA')
    
    select [ID]
    ,[NAME]
    ,[ADDRESS]
    ,REPLACE(LEFT(NAME, CHARINDEX('-', NAME)),'-',' ') as First_Name
    ,CASE 
    WHEN CHARINDEX('-',REVERSE(NAME))+ CHARINDEX('-',NAME) < LEN(NAME)
    THEN  SUBSTRING(NAME, CHARINDEX('-', (NAME)) + 1, LEN(NAME) - CHARINDEX('-' 
    , REVERSE(NAME)) - CHARINDEX('-', NAME))
      ELSE 'NULL
      END AS Middle_Name
    ,REPLACE(REVERSE( SUBSTRING( REVERSE(NAME), 1, CHARINDEX('- 
    ',REVERSE(NAME)))), '-','') AS Last_Name 
    ,REPLACE(LEFT(ADDRESS, CHARINDEX('|', ADDRESS)),'|',' ') AS Locality
    ,CASE 
        WHEN CHARINDEX('|',REVERSE(ADDRESS))+ CHARINDEX('|',ADDRESS) < 
      LEN(ADDRESS) 
     THEN SUBSTRING(ADDRESS, CHARINDEX('|', (ADDRESS))+1, LEN(ADDRESS)- 
    CHARINDEX('|', REVERSE(ADDRESS))-CHARINDEX('|',ADDRESS))
      ELSE 'Null' 
    END AS STATE
    ,REPLACE(REVERSE(SUBSTRING(REVERSE(ADDRESS),1 
    ,CHARINDEX('|',REVERSE(ADDRESS)))),'|','') AS Country
     FROM DETAILS
      SELECT CHARINDEX('-', REVERSE(NAME)) AS LAST,CHARINDEX('-',NAME)AS FIRST, 
     LEN(NAME) AS LENGTH
     FROM DETAILS
    

    -- LET ME KNOW IF YOU HAVE DOUBTS UNDERSTANDING THE CODE

    0 讨论(0)
  • 2020-11-29 09:13

    using a UDF makes the most sense it's flexible for all projects, the one I generally use is at my blog,

    http://sqlthis.blogspot.com/2005/02/list-to-table.html

    because it's written to take your input string and a delimiter, it can be any single character for a delimiter. I wrote the one at the link above, but then afterwards I found many sites publishing a similar solution, so parts may have been inspired from forums that I am a member of...

    it works, and hopefully it will work for you as well.

    edit 2017-08-09 as suggested, I've cloned the code block below. thanks!

    CREATE FUNCTION udfListToTable (@HList VarChar(2000), @Delimiter CHAR(1))
    RETURNS @ListTable TABLE (Field1 VARCHAR(6))
      AS
      BEGIN
      --By: Francisco Tapia
      --Date: 2/1/2005
      --Purpose: To convert a Comma delimited text to a Temp Variable table To help avoid dynamic sql
      -- Instead you can join the temp table or use it in your where clause if a field is IN the subquery
           DECLARE @FieldText as VarChar(6)
    
           IF RIGHT(RTRIM(@HLIST),1) <>@Delimiter
           SET @HList = @HList + @Delimiter
    
           WHILE CHARINDEX(@Delimiter, @HList) > 0
           BEGIN
                IF CHARINDEX(@Delimiter, @HList) > 0
                BEGIN
                     SELECT @FieldText =LEFT(@HList, CHARINDEX(@Delimiter, @HList)-1)
                END
           ELSE 
           BEGIN
                SELECT @FieldText = RTRIM(LTRIM(@HList))
           END
           --Insert into Variable Table
           INSERT INTO @ListTable(Field1)
           SELECT RTRIM(LTRIM(@FieldText))
           --Remove Item from list
           SELECT @HList = RIGHT(RTRIM(@HList), LEN(RTRIM(@HList)) -
                CHARINDEX(@Delimiter, @HList))
           END 
           RETURN
      END
    
    0 讨论(0)
提交回复
热议问题