【问题标题】:Split function equivalent in T-SQL?T-SQL中等效的拆分函数?
【发布时间】:2010-10-16 09:35:55
【问题描述】:

我希望将 '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15...'(逗号分隔)拆分为表格或表变量。

有没有人有一个函数可以连续返回每一个?

【问题讨论】:

标签: sql sql-server tsql sql-server-2008


【解决方案1】:

这对我很有用https://www.sqlshack.com/the-string-split-function-in-sql-server/

经过两个小时的研究,这是最简单的解决方案(不使用 XML 等)。

你应该只记得在 from 之后使用 string_split。

DROP TABLE IF EXISTS #Countries
GO
DROP TABLE IF EXISTS #CityList
GO
CREATE TABLE #Countries
(Continent VARCHAR(100),
Country VARCHAR(100))
GO
CREATE TABLE #CityList
(Country VARCHAR(100),
City VARCHAR(5000))
GO
INSERT INTO  #Countries
VALUES('Europe','France'),('Europe','Germany') 
 
INSERT INTO #CityList
VALUES('France','Paris,Marsilya,Lyon,Lille,Nice'), ('Germany','Berlin,Hamburg,Munih,Frankfurt,Koln')
 
SELECT 
CN.Continent,CN.Country,value
FROM #CityList CL CROSS APPLY string_split(CL.City,',')  INNER JOIN 
#Countries CN ON  CL.Country = CN.Country
 
 
DROP TABLE IF EXISTS #Countries
GO
DROP TABLE IF EXISTS #CityList

【讨论】:

    【解决方案2】:

    这个简单的 CTE 将提供所需的内容:

    DECLARE @csv varchar(max) = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15';
    --append comma to the list for CTE to work correctly
    SET @csv = @csv + ',';
    --remove double commas (empty entries)
    SET @csv = replace(@csv, ',,', ',');
    WITH CteCsv AS (
        SELECT CHARINDEX(',', @csv) idx, SUBSTRING(@csv, 1, CHARINDEX(',', @csv) - 1) [Value]
        UNION ALL
        SELECT CHARINDEX(',', @csv, idx + 1), SUBSTRING(@csv, idx + 1, CHARINDEX(',', @csv, idx + 1) - idx - 1) FROM CteCsv
        WHERE CHARINDEX(',', @csv, idx + 1) > 0
    )
    
    SELECT [Value] FROM CteCsv
    

    【讨论】:

    • @jinsungy 你可能想看看这个答案,它比公认的答案更有效,更简单。
    【解决方案3】:

    这里使用 tally table 是 Jeff Moden 的一个拆分字符串函数(最好的方法)

    CREATE FUNCTION [dbo].[DelimitedSplit8K]
            (@pString VARCHAR(8000), @pDelimiter CHAR(1))
    RETURNS TABLE WITH SCHEMABINDING AS
     RETURN
    --===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
         -- enough to cover NVARCHAR(4000)
      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
                    ),                          --10E+1 or 10 rows
           E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
           E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
     cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                         -- for both a performance gain and prevention of accidental "overruns"
                     SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                    ),
    cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                     SELECT 1 UNION ALL
                     SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                    ),
    cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                     SELECT s.N1,
                            ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                       FROM cteStart s
                    )
    --===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
     SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
            Item       = SUBSTRING(@pString, l.N1, l.L1)
       FROM cteLen l
    ;
    

    转自Tally OH! An Improved SQL 8K “CSV Splitter” Function

    【讨论】:

      【解决方案4】:

      这是你问的分割函数

      CREATE FUNCTION [dbo].[split](
                @delimited NVARCHAR(MAX),
                @delimiter NVARCHAR(100)
              ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
              AS
              BEGIN
                DECLARE @xml XML
                SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'
      
                INSERT INTO @t(val)
                SELECT  r.value('.','varchar(MAX)') as item
                FROM  @xml.nodes('/t') as records(r)
                RETURN
              END
      

      像这样执行函数

      select * from dbo.split('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15',',')
      

      【讨论】:

        【解决方案5】:

        您已标记此 SQL Server 2008,但此问题的未来访问者(使用 SQL Server 2016+)可能想了解STRING_SPLIT

        有了这个新的内置函数,您现在可以使用

        SELECT TRY_CAST(value AS INT)
        FROM   STRING_SPLIT ('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15', ',') 
        

        此功能的一些限制和一些有希望的性能测试结果在this blog post by Aaron Bertrand

        【讨论】:

          【解决方案6】:

          试试这个

          DECLARE @xml xml, @str varchar(100), @delimiter varchar(10)
          SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
          SET @delimiter = ','
          SET @xml = cast(('<X>'+replace(@str, @delimiter, '</X><X>')+'</X>') as xml)
          SELECT C.value('.', 'varchar(10)') as value FROM @xml.nodes('X') as X(C)
          

          DECLARE @str varchar(100), @delimiter varchar(10)
          SET @str = '1,2,3,4,5,6,7,8,9,10,11,12,13,14,15'
          SET @delimiter = ','
          ;WITH cte AS
          (
              SELECT 0 a, 1 b
              UNION ALL
              SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter)
              FROM CTE
              WHERE b > a
          )
          SELECT SUBSTRING(@str, a,
          CASE WHEN b > LEN(@delimiter) 
              THEN b - a - LEN(@delimiter) 
              ELSE LEN(@str) - a + 1 END) value      
          FROM cte WHERE a > 0
          

          这里有更多相同的方法How to split comma delimited string?

          【讨论】:

          • 对于搜索通用字符串拆分器的任何人的注意事项:这里给出的第一个解决方案不是通用字符串拆分器 - 只有当您确定输入永远不会包含 &lt;&gt;&amp;(例如输入是一个整数序列)。以上三个字符中的任何一个都会导致您收到解析错误而不是预期结果。
          • 事件与 miroxlav 提到的问题(应该可以通过一些想法解决),这绝对是我找到的最具创意的解决方案之一(第一个)!非常好!
          • SELECT b, CHARINDEX(@delimiter, @str, b) + LEN(@delimiter) 实际上应该是 SELECT b, CHARINDEX(@delimiter, @str, b+1) + LEN(@delimiter)b+1 有很大的不同。在此处使用空格作为分隔符进行了测试,如果没有此修复,则无法正常工作。
          • @miroxlav 另外,根据我的经验,使用 XML 拆分字符串是一个非常昂贵的弯路。
          • 很好的解决方案!值得注意的是,用户可能希望添加 MAXRECURSION option 以拆分 100 多个部分,将 LEN 替换为 stackoverflow.com/q/2025585 中的内容以处理空格,并排除 NULL 行以用于 NULL 输入。
          【解决方案7】:
          CREATE FUNCTION Split
          (
            @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 splitted 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 dbo.Split(N'1,2,3,4,6',',')
          

          【讨论】:

            【解决方案8】:
            DECLARE
                @InputString NVARCHAR(MAX) = 'token1,token2,token3,token4,token5'
                , @delimiter varchar(10) = ','
            
            DECLARE @xml AS XML = CAST(('<X>'+REPLACE(@InputString,@delimiter ,'</X><X>')+'</X>') AS XML)
            SELECT C.value('.', 'varchar(10)') AS value
            FROM @xml.nodes('X') as X(C)
            

            此回复的来源: http://sqlhint.com/sqlserver/how-to/best-split-function-tsql-delimited

            【讨论】:

            • 虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
            • @Xavi:好的,我已经包含了答案的基本部分。感谢您的提示。
            【解决方案9】:

            你在sql server中写这个函数,问题就解决了。

            http://csharpdotnetsol.blogspot.in/2013/12/csv-function-in-sql-server-for-divide.html

            【讨论】:

            • 不要只复制链接...这不是好问题的标志...您需要详细解释答案
            【解决方案10】:
            /* *Object:  UserDefinedFunction [dbo].[Split]    Script Date: 10/04/2013 18:18:38* */
            SET ANSI_NULLS ON
            GO
            SET QUOTED_IDENTIFIER ON
            GO
            ALTER FUNCTION [dbo].[Split]
            (@List varchar(8000),@SplitOn Nvarchar(5))
            RETURNS @RtnValue table
            (Id int identity(1,1),Value nvarchar(100))
            AS
            BEGIN
                Set @List = Replace(@List,'''','')
                While (Charindex(@SplitOn,@List)>0)
                Begin
            
                Insert Into @RtnValue (value)
                Select
                Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1)))
            
                Set @List = Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List))
                End
            
                Insert Into @RtnValue (Value)
                Select Value = ltrim(rtrim(@List))
            
                Return
            END
            go
            
            Select *
            From [Clv].[Split] ('1,2,3,3,3,3,',',')
            GO
            

            【讨论】:

              【解决方案11】:

              这是另一个真正没有任何限制的版本(例如:使用 xml 方法时的特殊字符,CTE 方法中的记录数)并且基于对源字符串平均长度为 4000 的 10M+ 记录的测试,它运行得更快. 希望这能有所帮助。

              Create function [dbo].[udf_split] (
                  @ListString nvarchar(max),
                  @Delimiter  nvarchar(1000),
                  @IncludeEmpty bit) 
              Returns @ListTable TABLE (ID int, ListValue nvarchar(1000))
              AS
              BEGIN
                  Declare @CurrentPosition int, @NextPosition int, @Item nvarchar(max), @ID int, @L int
                  Select @ID = 1,
                 @L = len(replace(@Delimiter,' ','^')),
                          @ListString = @ListString + @Delimiter,
                          @CurrentPosition = 1 
                  Select @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
                 While @NextPosition > 0 Begin
                 Set  @Item = LTRIM(RTRIM(SUBSTRING(@ListString, @CurrentPosition, @NextPosition-@CurrentPosition)))
                 If      @IncludeEmpty=1 or LEN(@Item)>0 Begin 
                   Insert Into @ListTable (ID, ListValue) Values (@ID, @Item)
                   Set @ID = @ID+1
                 End
                 Set  @CurrentPosition = @NextPosition+@L
                 Set  @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
                End
                  RETURN
              END
              

              【讨论】:

                【解决方案12】:
                CREATE Function [dbo].[CsvToInt] ( @Array varchar(4000)) 
                returns @IntTable table 
                (IntValue int)
                AS
                begin
                declare @separator char(1)
                set @separator = ','
                declare @separator_position int 
                declare @array_value varchar(4000) 
                
                set @array = @array + ','
                
                while patindex('%,%' , @array) <> 0 
                begin
                
                select @separator_position = patindex('%,%' , @array)
                select @array_value = left(@array, @separator_position - 1)
                
                Insert @IntTable
                Values (Cast(@array_value as int))
                select @array = stuff(@array, 1, @separator_position, '')
                end
                

                【讨论】:

                  【解决方案13】:

                  This blog 提供了一个在 T-SQL 中使用 XML 的非常好的解决方案。

                  这是我根据该博客提出的函数(根据需要更改函数名称和结果类型转换):

                  SET ANSI_NULLS ON
                  GO
                  SET QUOTED_IDENTIFIER ON
                  GO
                  CREATE FUNCTION [dbo].[SplitIntoBigints]
                  (@List varchar(MAX), @Splitter char)
                  RETURNS TABLE 
                  AS
                  RETURN 
                  (
                      WITH SplittedXML AS(
                          SELECT CAST('<v>' + REPLACE(@List, @Splitter, '</v><v>') + '</v>' AS XML) AS Splitted
                      )
                      SELECT x.v.value('.', 'bigint') AS Value
                      FROM SplittedXML
                      CROSS APPLY Splitted.nodes('//v') x(v)
                  )
                  GO
                  

                  【讨论】:

                    【解决方案14】:

                    对于那些熟悉该功能的人来说,这与 .NET 最相似:

                    CREATE FUNCTION dbo.[String.Split]
                    (
                        @Text VARCHAR(MAX),
                        @Delimiter VARCHAR(100),
                        @Index INT
                    )
                    RETURNS VARCHAR(MAX)
                    AS BEGIN
                        DECLARE @A TABLE (ID INT IDENTITY, V VARCHAR(MAX));
                        DECLARE @R VARCHAR(MAX);
                        WITH CTE AS
                        (
                        SELECT 0 A, 1 B
                        UNION ALL
                        SELECT B, CONVERT(INT,CHARINDEX(@Delimiter, @Text, B) + LEN(@Delimiter))
                        FROM CTE
                        WHERE B > A
                        )
                        INSERT @A(V)
                        SELECT SUBSTRING(@Text,A,CASE WHEN B > LEN(@Delimiter) THEN B-A-LEN(@Delimiter) ELSE LEN(@Text) - A + 1 END) VALUE      
                        FROM CTE WHERE A >0
                    
                        SELECT      @R
                        =           V
                        FROM        @A
                        WHERE       ID = @Index + 1
                        RETURN      @R
                    END
                    
                    SELECT dbo.[String.Split]('121,2,3,0',',',1) -- gives '2'
                    

                    【讨论】:

                      【解决方案15】:

                      我很想挤进我最喜欢的解决方案。结果表将包含 2 列:PosIdx 表示找到的整数的位置;和整数值。

                      
                      create function FnSplitToTableInt
                      (
                          @param nvarchar(4000)
                      )
                      returns table as
                      return
                          with Numbers(Number) as 
                          (
                              select 1 
                              union all 
                              select Number + 1 from Numbers where Number < 4000
                          ),
                          Found as
                          (
                              select 
                                  Number as PosIdx,
                                  convert(int, ltrim(rtrim(convert(nvarchar(4000), 
                                      substring(@param, Number, 
                                      charindex(N',' collate Latin1_General_BIN, 
                                      @param + N',', Number) - Number))))) as Value
                              from   
                                  Numbers 
                              where  
                                  Number <= len(@param)
                              and substring(N',' + @param, Number, 1) = N',' collate Latin1_General_BIN
                          )
                          select 
                              PosIdx, 
                              case when isnumeric(Value) = 1 
                                  then convert(int, Value) 
                                  else convert(int, null) end as Value 
                          from 
                              Found
                      

                      它通过使用递归 CTE 作为位置列表来工作,默认情况下从 1 到 100。如果您需要处理超过 100 的字符串,只需使用 'option (maxrecursion 4000)' 调用此函数,如下所示:

                      
                      select * from FnSplitToTableInt
                      (
                          '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' + 
                          '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
                          '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
                          '9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' +
                          '9, 8, 7, 6, 5, 4, 3, 2, 1, 0'
                      ) 
                      option (maxrecursion 4000)
                      

                      【讨论】:

                      • +1 用于提及 maxrecursion 选项。显然,在生产环境中应谨慎使用重递归,但它非常适合使用 CTE 执行繁重的数据导入或转换任务。
                      【解决方案16】:

                      这里有一些老式的解决方案:

                      /*
                          Splits string into parts delimitered with specified character.
                      */
                      CREATE FUNCTION [dbo].[SDF_SplitString]
                      (
                          @sString nvarchar(2048),
                          @cDelimiter nchar(1)
                      )
                      RETURNS @tParts TABLE ( part nvarchar(2048) )
                      AS
                      BEGIN
                          if @sString is null return
                          declare @iStart int,
                                  @iPos int
                          if substring( @sString, 1, 1 ) = @cDelimiter 
                          begin
                              set @iStart = 2
                              insert into @tParts
                              values( null )
                          end
                          else 
                              set @iStart = 1
                          while 1=1
                          begin
                              set @iPos = charindex( @cDelimiter, @sString, @iStart )
                              if @iPos = 0
                                  set @iPos = len( @sString )+1
                              if @iPos - @iStart > 0          
                                  insert into @tParts
                                  values  ( substring( @sString, @iStart, @iPos-@iStart ))
                              else
                                  insert into @tParts
                                  values( null )
                              set @iStart = @iPos+1
                              if @iStart > len( @sString ) 
                                  break
                          end
                          RETURN
                      
                      END
                      

                      在 SQL Server 2008 中,您可以使用 .NET 代码实现相同的目的。也许它会工作得更快,但这种方法肯定更容易管理。

                      【讨论】:

                      • 谢谢,我也想知道。这里有错误吗?我大概在 6 年前编写了这段代码,从那时起它就可以正常工作了。
                      • 我同意。当您不想(或根本不能)参与创建表类型参数时,这是一个非常好的解决方案,在我的实例中就是这种情况。 DBA 已锁定该功能并且不允许使用它。谢谢异或!
                      • DECLARE VarString NVARCHAR(2048) = 'Mike/John/Miko/Matt';声明 CaracString NVARCHAR(1) = '/'; SELECT * FROM dbo.FnSplitString (VarString, CaracString)
                      猜你喜欢
                      • 1970-01-01
                      • 2019-08-27
                      • 2021-10-10
                      • 1970-01-01
                      • 2021-05-13
                      • 1970-01-01
                      • 2014-02-05
                      • 1970-01-01
                      相关资源
                      最近更新 更多