T-SQL 拆分字符串

T-SQL split string

提问人:Lee Grindon 提问时间:6/6/2012 最后编辑:HadiLee Grindon 更新时间:1/17/2022 访问量:653466

问:

我有一个 SQL Server 2008 R2 列,其中包含一个字符串,我需要用逗号拆分。我在 StackOverflow 上看到了很多答案,但它们在 R2 中都不起作用。我已经确保我对任何拆分函数示例都有选择权限。任何帮助,非常感谢。

sql-server sql-server-2008 拆分

评论

7赞 nurettin 6/6/2012
这是我喜欢 stackoverflow.com/a/1846561/227755 的百万个答案之一
2赞 Aaron Bertrand 6/6/2012
“它们都不起作用”是什么意思?你能说得更具体一点吗?
0赞 Lee Grindon 6/6/2012
安迪确实为我指出了正确的方向,因为我错误地执行了该功能。这就是为什么其他堆栈答案都不起作用的原因。我的错。
2赞 Prahalad Gaggar 7/10/2013
SQL 中 Split 字符串的可能重复项
0赞 jpaugh 11/1/2016
“Master Data Services”加载项中有一个功能可能会有所帮助。当然值得研究mdq.RegexSplit

答:

292赞 Andy Robinson 6/6/2012 #1

我以前使用过这个 SQL,它可能对你有用:-

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE CHARINDEX(',', @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END

并使用它:-

SELECT * FROM dbo.splitstring('91,12,65,78,56,789')

评论

3赞 CF_Maintainer 12/27/2013
非常感谢安迪。我对您的脚本进行了小的改进,以允许该函数在拆分字符串中的特定索引处返回一个项目。它仅在解析第一列的结构时才有用。gist.github.com/klimaye/8147193
1赞 mpag 6/17/2016
在这里的 github 页面上发布了一些改进(带有支持测试用例)。当我有足够的代表超过帖子“保护”时,我会在这个 Stack Overflow 线程中将其作为答案发布
10赞 Shnugo 2/2/2017
虽然这是一个很好的答案,但它已经过时了......程序方法(尤其是循环)是要避免的......值得研究一下更新的答案......
1赞 Pasi Savolainen 9/18/2017
这不适用于select * from dbo.splitstring('')
4赞 Sean Lange 4/27/2018
我完全同意@Shnugo。循环分离器可以工作,但速度非常慢。像这样的东西 sqlservercentral.com/articles/Tally+Table/72993 要好得多。可以在此处找到其他一些出色的基于集合的选项。sqlperformance.com/2012/07/t-sql-queries/split-strings
7赞 AviG 11/9/2012 #2

如果将

WHILE CHARINDEX(',', @stringToSplit) > 0

WHILE LEN(@stringToSplit) > 0

您可以消除 while 循环之后的最后一个插入!

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE LEN(@stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @stringToSplit)


if @pos = 0
        SELECT @pos = LEN(@stringToSplit)


  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 RETURN
END

评论

0赞 Microsoft Developer 4/10/2013
这将导致最后一个元素的最后一个字符被截断。即“AL,AL”将变为“AL” |“A”,即“ABC,ABC,ABC”将变为“ABC” |“ABC” |“AB”
0赞 mpag 6/16/2016
附加似乎可以解决该问题。但是,除非您同时添加到 SUBSTRING 的第三个参数,否则将返回。或者,您可以将该分配替换为+1SELECT @pos = LEN(@stringToSplit)SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)Invalid length parameter passed to the LEFT or SUBSTRING function+1SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) --MAX len of nvarchar is 4000
1赞 mpag 6/17/2016
在这里的 github 页面上发布了一些改进(带有支持测试用例)。当我有足够的代表超过帖子“保护”时,我会在这个 Stack Overflow 线程中将其作为答案发布
0赞 Gemunu R Wickremasinghe 6/23/2018
我也注意到了特里上面指出的问题。但是@AviG给出的逻辑非常酷,以至于它不会在一长串令牌的中间失败。尝试此测试调用以验证(此调用应返回 969 个令牌) select * from dbo.splitstring('token1,token2,,,,,,,,token969') 然后我尝试了 mpag 给出的代码来检查上面相同调用的结果,发现它只能返回 365 个令牌。最后,我修复了上面 AviG 的代码,并在下面发布了无错误功能作为新回复,因为这里的评论只允许有限的文本。检查我名下的回复以尝试一下。
2赞 p.s.w.g 7/17/2013 #3

我最近不得不写这样的东西。这是我想出的解决方案。它适用于任何分隔符字符串,我认为它的性能会稍微好一些:

CREATE FUNCTION [dbo].[SplitString] 
    ( @string nvarchar(4000)
    , @delim nvarchar(100) )
RETURNS
    @result TABLE 
        ( [Value] nvarchar(4000) NOT NULL
        , [Index] int NOT NULL )
AS
BEGIN
    DECLARE @str nvarchar(4000)
          , @pos int 
          , @prv int = 1

    SELECT @pos = CHARINDEX(@delim, @string)
    WHILE @pos > 0
    BEGIN
        SELECT @str = SUBSTRING(@string, @prv, @pos - @prv)
        INSERT INTO @result SELECT @str, @prv

        SELECT @prv = @pos + LEN(@delim)
             , @pos = CHARINDEX(@delim, @string, @pos + 1)
    END

    INSERT INTO @result SELECT SUBSTRING(@string, @prv, 4000), @prv
    RETURN
END
2赞 Torsten B. Hagemann 7/19/2013 #4

使用 CTE 的解决方案,如果有人需要它(除了我,他显然这样做了,这就是我写它的原因)。

declare @StringToSplit varchar(100) = 'Test1,Test2,Test3';
declare @SplitChar varchar(10) = ',';

with StringToSplit as (
  select 
      ltrim( rtrim( substring( @StringToSplit, 1, charindex( @SplitChar, @StringToSplit ) - 1 ) ) ) Head
    , substring( @StringToSplit, charindex( @SplitChar, @StringToSplit ) + 1, len( @StringToSplit ) ) Tail

  union all

  select
      ltrim( rtrim( substring( Tail, 1, charindex( @SplitChar, Tail ) - 1 ) ) ) Head
    , substring( Tail, charindex( @SplitChar, Tail ) + 1, len( Tail ) ) Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) > 0

  union all

  select
      ltrim( rtrim( Tail ) ) Head
    , '' Tail
  from StringToSplit
  where charindex( @SplitChar, Tail ) = 0
    and len( Tail ) > 0
)
select Head from StringToSplit
-1赞 MiH 10/11/2013 #5
ALTER FUNCTION [dbo].func_split_string
(
    @input as varchar(max),
    @delimiter as varchar(10) = ";"

)
RETURNS @result TABLE
(
    id smallint identity(1,1),
    csv_value varchar(max) not null
)
AS
BEGIN
    DECLARE @pos AS INT;
    DECLARE @string AS VARCHAR(MAX) = '';

    WHILE LEN(@input) > 0
    BEGIN           
        SELECT @pos = CHARINDEX(@delimiter,@input);

        IF(@pos<=0)
            select @pos = len(@input)

        IF(@pos <> LEN(@input))
            SELECT @string = SUBSTRING(@input, 1, @pos-1);
        ELSE
            SELECT @string = SUBSTRING(@input, 1, @pos);

        INSERT INTO @result SELECT @string

        SELECT @input = SUBSTRING(@input, @pos+len(@delimiter), LEN(@input)-@pos)       
    END
    RETURN  
END
78赞 Aaron Bertrand 11/13/2013 #6

除了递归 CTE 和 while 循环之外,有没有人考虑过更基于集合的方法?请注意,此函数是针对问题编写的,该问题基于 SQL Server 2008 和逗号作为分隔符。在 SQL Server 2016 及更高版本(以及兼容级别 130 及更高版本)中,STRING_SPLIT() 是更好的选择

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE
AS
  RETURN ( SELECT [Value] FROM 
  ( 
    SELECT [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number],
      CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number])))
    FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
      FROM sys.all_columns) AS x WHERE Number <= LEN(@List)
      AND SUBSTRING(@Delim + @List, [Number], DATALENGTH(@Delim)/2) = @Delim
    ) AS y
  );
GO

如果要避免字符串长度限制为 <= 中的行数(在 SQL Server 2017 中为 9,980 英寸;在你自己的用户数据库中要高得多),可以使用其他方法来派生数字,例如生成自己的数字表。在无法使用系统表或创建自己的表的情况下,还可以使用递归 CTE:sys.all_columnsmodel

CREATE FUNCTION dbo.SplitString
(
  @List     nvarchar(max),
  @Delim    nvarchar(255)
)
RETURNS TABLE WITH SCHEMABINDING
AS
   RETURN ( WITH n(n) AS (SELECT 1 UNION ALL SELECT n+1 
       FROM n WHERE n <= LEN(@List))
       SELECT [Value] = SUBSTRING(@List, n, 
       CHARINDEX(@Delim, @List + @Delim, n) - n)
       FROM n WHERE n <= LEN(@List)
      AND SUBSTRING(@Delim + @List, n, DATALENGTH(@Delim)/2) = @Delim
   );
GO

但是,您必须将 (or ) 追加到外部查询,以避免 100 个字符>字符串的递归错误。如果这也不是一个好的选择,那么请参阅评论中指出的这个答案,或者如果你需要一个有序的拆分字符串函数,请参阅这个答案OPTION (MAXRECURSION 0)MAXRECURSION <longest possible string length if < 32768>

(此外,分隔符必须是 .仍在研究原因。NCHAR(<=1228)

更多关于拆分函数的信息,为什么(并证明)循环和递归 CTE 无法扩展,以及更好的替代方案,如果你要拆分来自应用层的字符串:

评论

1赞 SylvainL 9/15/2014
此过程中存在一个小错误,即字符串末尾会有一个 null 值 - 例如在 '1,2,,4' 中 - 因为最终值未被解析。要更正此错误,应将表达式“WHERE Number <= LEN(@List)”替换为“WHERE Number <= LEN(@List) + 1”。
0赞 Aaron Bertrand 9/15/2014
@SylvainL我想这取决于你想要什么行为。根据我的经验,大多数人都希望忽略任何尾随逗号,因为它们并不真正代表真正的元素(您需要多少个空白字符串的副本)?无论如何,执行此操作的真正方法(如果您遵循第二个链接)是无论如何都要在慢速 T-SQL 中拆分丑陋的大字符串。
1赞 SylvainL 9/15/2014
就像你说的,大多数人都想忽略任何尾随逗号,但唉,不是全部。我想一个更完整的解决方案是添加一个参数来指定在这种情况下要做什么,但我的评论只是一个小说明,以确保没有人忘记这种可能性,因为它在许多情况下可能是非常真实的。
0赞 Patrick Desjardins 6/10/2015
我对该函数有一个奇怪的行为。如果我直接使用字符串作为参数 - 它可以工作。如果我有一个 varchar,它没有。你可以很容易地复制:将 invarchar 声明为 varchar set invarchar = 'ta;机 管 局;qq' SELECT [dbo] 中的值。[拆分字符串](invarchar, ';')SELECT [dbo] 中的值。[拆分字符串]('ta;机 管 局;qq', ';')
0赞 knuckles 10/10/2016
我喜欢这种方法,但是如果返回的对象数少于输入字符串中的字符数,那么它将截断字符串并且值将丢失。由于只是被用作生成行的一点技巧,那么有更好的方法可以做到这一点,例如这个答案sys.all_objectssys.all_objects
1赞 Tom Regan 11/15/2013 #7

这是更狭隘的定制。当我这样做时,我通常有一个逗号分隔的唯一 ID(INT 或 BIGINT)列表,我想将其转换为一个表,以用作另一个主键为 INT 或 BIGINT 的表的内部联接。我想要返回一个内联表值函数,以便我尽可能有效地联接。

示例用法为:

 DECLARE @IDs VARCHAR(1000);
 SET @IDs = ',99,206,124,8967,1,7,3,45234,2,889,987979,';
 SELECT me.Value
 FROM dbo.MyEnum me
 INNER JOIN dbo.GetIntIdsTableFromDelimitedString(@IDs) ids ON me.PrimaryKey = ids.ID

我从 http://sqlrecords.blogspot.com/2012/11/converting-delimited-list-to-table.html 中窃取了这个想法,将其更改为内联表值并转换为 INT。

create function dbo.GetIntIDTableFromDelimitedString
    (
    @IDs VARCHAR(1000)  --this parameter must start and end with a comma, eg ',123,456,'
                        --all items in list must be perfectly formatted or function will error
)
RETURNS TABLE AS
 RETURN

SELECT
    CAST(SUBSTRING(@IDs,Nums.number + 1,CHARINDEX(',',@IDs,(Nums.number+2)) - Nums.number - 1) AS INT) AS ID 
FROM   
     [master].[dbo].[spt_values] Nums
WHERE Nums.Type = 'P' 
AND    Nums.number BETWEEN 1 AND DATALENGTH(@IDs)
AND    SUBSTRING(@IDs,Nums.number,1) = ','
AND    CHARINDEX(',',@IDs,(Nums.number+1)) > Nums.number;

GO
0赞 Mark Giaconia 12/6/2013 #8

这是一个可以使用 patindex 在模式上拆分的版本,这是对上面帖子的简单改编。我有一个案例,我需要拆分一个包含多个分隔符字符的字符串。


alter FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(1000), @splitPattern varchar(10) )
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

 DECLARE @name NVARCHAR(255)
 DECLARE @pos INT

 WHILE PATINDEX(@splitPattern, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = PATINDEX(@splitPattern, @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
select * from dbo.splitstring('stringa/stringb/x,y,z','%[/,]%');

结果如下所示

字符串 字符串b x y z

13赞 CorvetteGuru 1/24/2014 #9

我需要一种快速的方法来摆脱邮政编码+4

UPDATE #Emails 
  SET ZIPCode = SUBSTRING(ZIPCode, 1, (CHARINDEX('-', ZIPCODE)-1)) 
  WHERE ZIPCode LIKE '%-%'

无程序...没有 UDF...只是一个紧凑的小内联命令,可以执行它必须执行的操作。不花哨,不优雅。

根据需要更改分隔符等,它将适用于任何事情。

评论

5赞 codeulike 4/29/2015
这不是问题所在。OP 的值类似于“234,542,23”,他们希望将其拆分为三行......第一排:234,第二排:542,第三排:23。在 SQL 中这是一件棘手的事情。
0赞 Kaly 4/24/2014 #10

我个人使用这个功能:

ALTER FUNCTION [dbo].[CUST_SplitString]
(
    @String NVARCHAR(4000),
    @Delimiter NCHAR(1)
)
RETURNS TABLE 
AS
RETURN 
(
    WITH Split(stpos,endpos) 
    AS(
        SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
        UNION ALL
        SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) 
        FROM Split
        WHERE endpos > 0
    )
    SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
        'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
    FROM Split
)
0赞 Gouri Shankar Aechoor 7/30/2014 #11

我按照这里的要求开发了一个双拆分器(需要两个拆分字符)。在此线程中可能具有一些价值,因为它是与字符串拆分相关的查询引用最多的查询。

CREATE FUNCTION uft_DoubleSplitter 
(   
    -- Add the parameters for the function here
    @String VARCHAR(4000), 
    @Splitter1 CHAR,
    @Splitter2 CHAR
)
RETURNS @Result TABLE (Id INT,MId INT,SValue VARCHAR(4000))
AS
BEGIN
DECLARE @FResult TABLE(Id INT IDENTITY(1, 1),
                   SValue VARCHAR(4000))
DECLARE @SResult TABLE(Id INT IDENTITY(1, 1),
                   MId INT,
                   SValue VARCHAR(4000))
SET @String = @String+@Splitter1

WHILE CHARINDEX(@Splitter1, @String) > 0
    BEGIN
       DECLARE @WorkingString VARCHAR(4000) = NULL

       SET @WorkingString = SUBSTRING(@String, 1, CHARINDEX(@Splitter1, @String) - 1)
       --Print @workingString

       INSERT INTO @FResult
       SELECT CASE
            WHEN @WorkingString = '' THEN NULL
            ELSE @WorkingString
            END

       SET @String = SUBSTRING(@String, LEN(@WorkingString) + 2, LEN(@String))

    END
IF ISNULL(@Splitter2, '') != ''
    BEGIN
       DECLARE @OStartLoop INT
       DECLARE @OEndLoop INT

       SELECT @OStartLoop = MIN(Id),
            @OEndLoop = MAX(Id)
       FROM @FResult

       WHILE @OStartLoop <= @OEndLoop
          BEGIN
             DECLARE @iString VARCHAR(4000)
             DECLARE @iMId INT

             SELECT @iString = SValue+@Splitter2,
                   @iMId = Id
             FROM @FResult
             WHERE Id = @OStartLoop

             WHILE CHARINDEX(@Splitter2, @iString) > 0
                BEGIN
                    DECLARE @iWorkingString VARCHAR(4000) = NULL

                    SET @IWorkingString = SUBSTRING(@iString, 1, CHARINDEX(@Splitter2, @iString) - 1)

                    INSERT INTO @SResult
                    SELECT @iMId,
                         CASE
                         WHEN @iWorkingString = '' THEN NULL
                         ELSE @iWorkingString
                         END

                    SET @iString = SUBSTRING(@iString, LEN(@iWorkingString) + 2, LEN(@iString))

                END

             SET @OStartLoop = @OStartLoop + 1
          END
       INSERT INTO @Result
       SELECT MId AS PrimarySplitID,
            ROW_NUMBER() OVER (PARTITION BY MId ORDER BY Mid, Id) AS SecondarySplitID ,
            SValue
       FROM @SResult
    END
ELSE
    BEGIN
       INSERT INTO @Result
       SELECT Id AS PrimarySplitID,
            NULL AS SecondarySplitID,
            SValue
       FROM @FResult
    END
RETURN

用法:

--FirstSplit
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&',NULL)

--Second Split
SELECT * FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===','&','=')

可能的用法(获取每个拆分的第二个值):

SELECT fn.SValue
FROM uft_DoubleSplitter('ValueA=ValueB=ValueC=ValueD==ValueE&ValueA=ValueB=ValueC===ValueE&ValueA=ValueB==ValueD===', '&', '=')AS fn
WHERE fn.mid = 2
33赞 Sarath Subramanian 1/26/2015 #12

最简单的方法是使用格式。XML

1.将字符串转换为没有表格的行

查询

DECLARE @String varchar(100) = 'String1,String2,String3'
-- To change ',' to any other delimeter, just change ',' to your desired one
DECLARE @Delimiter CHAR = ','    

SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT CAST ('<M>' + REPLACE(@String, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

结果

 x---------x
 | Value   |
 x---------x
 | String1 |
 | String2 |
 | String3 |
 x---------x

2. 从表中转换为每个 CSV 行都有一个 ID 的行

源表

 x-----x--------------------------x
 | Id  |           Value          |
 x-----x--------------------------x
 |  1  |  String1,String2,String3 |
 |  2  |  String4,String5,String6 |     
 x-----x--------------------------x

查询

-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
DECLARE @Delimiter CHAR = ','

SELECT ID,LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value' 
FROM  
(     
     SELECT ID,CAST ('<M>' + REPLACE(VALUE, @Delimiter, '</M><M>') + '</M>' AS XML) AS Data            
     FROM TABLENAME
) AS A 
CROSS APPLY Data.nodes ('/M') AS Split(a)

结果

 x-----x----------x
 | Id  |  Value   |
 x-----x----------x
 |  1  |  String1 |
 |  1  |  String2 |  
 |  1  |  String3 |
 |  2  |  String4 |  
 |  2  |  String5 |
 |  2  |  String6 |     
 x-----x----------x

评论

0赞 Shnugo 2/2/2017
如果包含禁止的字符,这种方法将中断......我刚刚发布了一个答案来克服这个问题。@String
1赞 abillon 2/24/2023
对我来说最好的答案。不需要函数!谢谢
1赞 Bryce 2/13/2015 #13

这里有一个正确的版本,但我认为添加一点容错会很好,以防它们有一个尾随逗号,并制作它,这样你就可以不将其用作函数,而是将其用作更大代码的一部分。以防万一您只使用一次并且不需要功能。这也适用于整数(这是我需要它的原因),因此您可能需要更改数据类型。

DECLARE @StringToSeperate VARCHAR(10)
SET @StringToSeperate = '1,2,5'

--SELECT @StringToSeperate IDs INTO #Test

DROP TABLE #IDs
CREATE TABLE #IDs (ID int) 

DECLARE @CommaSeperatedValue NVARCHAR(255) = ''
DECLARE @Position INT = LEN(@StringToSeperate)

--Add Each Value
WHILE CHARINDEX(',', @StringToSeperate) > 0
BEGIN
    SELECT @Position  = CHARINDEX(',', @StringToSeperate)  
    SELECT @CommaSeperatedValue = SUBSTRING(@StringToSeperate, 1, @Position-1)

    INSERT INTO #IDs 
    SELECT @CommaSeperatedValue

    SELECT @StringToSeperate = SUBSTRING(@StringToSeperate, @Position+1, LEN(@StringToSeperate)-@Position)

END

--Add Last Value
IF (LEN(LTRIM(RTRIM(@StringToSeperate)))>0)
BEGIN
    INSERT INTO #IDs
    SELECT SUBSTRING(@StringToSeperate, 1, @Position)
END

SELECT * FROM #IDs

评论

0赞 mpag 6/17/2016
如果你在循环之前,我认为你也许可以消除“添加最后一个值”块。另请参阅 github 上的 my sol'nSET @StringToSeperate = @StringToSeperate+','WHILE
0赞 jpaugh 11/1/2016
这是基于哪个答案?这里有很多答案,有点令人困惑。谢谢。
1赞 SNabi 4/17/2015 #14

我稍微修改了 +Andy Robinson 的功能。现在,您可以从返回表中仅选择所需的零件:

CREATE FUNCTION dbo.splitstring ( @stringToSplit VARCHAR(MAX) )

RETURNS

 @returnList TABLE ([numOrder] [tinyint] , [Name] [nvarchar] (500)) AS
BEGIN

 DECLARE @name NVARCHAR(255)

 DECLARE @pos INT

 DECLARE @orderNum INT

 SET @orderNum=0

 WHILE CHARINDEX('.', @stringToSplit) > 0

 BEGIN
    SELECT @orderNum=@orderNum+1;
  SELECT @pos  = CHARINDEX('.', @stringToSplit)  
  SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)

  INSERT INTO @returnList 
  SELECT @orderNum,@name

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
 END
    SELECT @orderNum=@orderNum+1;
 INSERT INTO @returnList
 SELECT @orderNum, @stringToSplit

 RETURN
END


Usage:

SELECT Name FROM dbo.splitstring('ELIS.YD.CRP1.1.CBA.MDSP.T389.BT') WHERE numOrder=5

-1赞 Abhinav 7/2/2015 #15

您可以使用此功能:

        CREATE FUNCTION SplitString
        (    
           @Input NVARCHAR(MAX),
           @Character CHAR(1)
          )
            RETURNS @Output TABLE (
            Item NVARCHAR(1000)
          )
        AS
        BEGIN

      DECLARE @StartIndex INT, @EndIndex INT
      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
GO
-3赞 Jovan MSFT 3/9/2016 #16

最简单的方法:

  1. 安装 SQL Server 2016Install SQL Server 2016
  2. 使用STRING_SPLIT https://msdn.microsoft.com/en-us/library/mt684588.aspx

它甚至可以在 :) 速成版中工作。

评论

0赞 Tomino 11/9/2016
不要忘记将“兼容级别”设置为 SQL Server 2016 (130) - 在 Management Studio 中,右键单击数据库、属性/选项/兼容级别。
1赞 Shawn Gavett 1/10/2017
原文说的是 SQL 2008 R2。安装 SQL 2016 可能不是一种选择
74赞 Pரதீப் 3/30/2016 #17

最后,在SQL Server 2016中,等待结束了,他们引入了拆分字符串函数: STRING_SPLIT

select * From STRING_SPLIT ('a,b', ',') cs 

所有其他拆分字符串的方法,如 XML、Tally 表、while 循环等。被这个功能震撼了。STRING_SPLIT

这是一篇性能比较的优秀文章:性能惊喜和假设:STRING_SPLIT

评论

6赞 mpag 6/17/2016
显然回答了如何为那些拥有更新服务器的人拆分字符串的问题,但是我们这些仍然停留在 2008/2008R2 上的人,将不得不在这里使用其他答案之一。
2赞 Luis Teijon 6/21/2017
您需要查看数据库中的兼容级别。如果低于 130,您将无法使用 STRING_SPLIT 函数。
2赞 CherryCoke 12/15/2017
实际上,如果兼容性不是 130,并且你运行的是 2016(或 Azure SQL),则可以使用以下命令将兼容性设置为 130: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
0赞 BurnsBA 2/18/2023
请注意文档:“输出行可以按任何顺序排列。在 SQL Server 2022 中添加了第三个参数,以返回包含结果的位置。ordinal
-1赞 Surya 6/24/2016 #18

这是一个示例,您可以将其用作函数,也可以将相同的逻辑放入过程中。 --SELECT * 来自 [dbo].fn_SplitString ;

CREATE FUNCTION [dbo].[fn_SplitString]
(@CSV VARCHAR(MAX), @Delimeter VARCHAR(100) = ',')
       RETURNS @retTable TABLE 
(

    [value] VARCHAR(MAX) NULL
)AS

BEGIN

DECLARE
       @vCSV VARCHAR (MAX) = @CSV,
       @vDelimeter VARCHAR (100) = @Delimeter;

IF @vDelimeter = ';'
BEGIN
    SET @vCSV = REPLACE(@vCSV, ';', '~!~#~');
    SET @vDelimeter = REPLACE(@vDelimeter, ';', '~!~#~');
END;

SET @vCSV = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@vCSV, '&', '&amp;'), '<', '&lt;'), '>', '&gt;'), '''', '&apos;'), '"', '&quot;');

DECLARE @xml XML;

SET @xml = '<i>' + REPLACE(@vCSV, @vDelimeter, '</i><i>') + '</i>';

INSERT INTO @retTable
SELECT
       x.i.value('.', 'varchar(max)') AS COLUMNNAME
  FROM @xml.nodes('//i')AS x(i);

 RETURN;
END;

评论

0赞 Shnugo 2/2/2017
如果包含禁止的字符,这种方法将中断......我刚刚发布了一个答案来克服这个问题。@vCSV
-1赞 mpag 8/12/2016 #19

/*

T-SQL 拆分字符串
的答案 基于 Andy RobinsonAviG
的回答 增强功能 ref: LEN 函数在 SQL Server
中不包括尾随空格 此“文件”应作为 Markdown 文件和 SQL 文件均有效


*/

    CREATE FUNCTION dbo.splitstring ( --CREATE OR ALTER
        @stringToSplit NVARCHAR(MAX)
    ) RETURNS @returnList TABLE ([Item] NVARCHAR (MAX))
    AS BEGIN
        DECLARE @name NVARCHAR(MAX)
        DECLARE @pos BIGINT
        SET @stringToSplit = @stringToSplit + ','             -- this should allow entries that end with a `,` to have a blank value in that "column"
        WHILE ((LEN(@stringToSplit+'_') > 1)) BEGIN           -- `+'_'` gets around LEN trimming terminal spaces. See URL referenced above
            SET @pos = COALESCE(NULLIF(CHARINDEX(',', @stringToSplit),0),LEN(@stringToSplit+'_')) -- COALESCE grabs first non-null value
            SET @name = SUBSTRING(@stringToSplit, 1, @pos-1)  --MAX size of string of type nvarchar is 4000 
            SET @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, 4000) -- With SUBSTRING fn (MS web): "If start is greater than the number of characters in the value expression, a zero-length expression is returned."
            INSERT INTO @returnList SELECT @name --additional debugging parameters below can be added
            -- + ' pos:' + CAST(@pos as nvarchar) + ' remain:''' + @stringToSplit + '''(' + CAST(LEN(@stringToSplit+'_')-1 as nvarchar) + ')'
        END
        RETURN
    END
    GO

/*

测试用例:请参阅上面引用的“增强功能”URL

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,b')

Item | L
---  | ---
a    | 1
     | 0
b    | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,,')

Item | L   
---  | ---
a    | 1
     | 0
     | 0

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, ')

Item | L   
---  | ---
a    | 1
     | 0
     | 1

SELECT *,LEN(Item+'_')-1 'L' from splitstring('a,, c ')

Item | L   
---  | ---
a    | 1
     | 0
 c   | 3

*/

评论

0赞 mpag 8/13/2019
回滚以遵循“此'文件'应作为 Markdown 文件和 SQL 文件均有效”
5赞 Igor Micev 1/29/2017 #20

所有使用某种循环(迭代)的字符串拆分函数的性能都很差。它们应该被替换为基于集合的解决方案。

此代码执行出色。

CREATE FUNCTION dbo.SplitStrings
(
   @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

评论

0赞 Shnugo 2/2/2017
如果包含禁止的字符,这种方法将中断......我刚刚发布了一个答案来克服这个问题。@List
0赞 KMC 7/26/2019
我赞成你的回答,因为你的回答使用空格作为分隔符,而得票最高的人则不行
7赞 Shnugo 2/2/2017 #21

XML 元素的常用方法在禁止字符的情况下会中断。这是一种将此方法用于任何类型的字符的方法,即使使用分号作为分隔符也是如此。

诀窍是,首先要让所有被禁止的角色正确逃脱。这就是为什么我将分隔符替换为魔术值以避免作为分隔符的麻烦的原因。SELECT SomeString AS [*] FOR XML PATH('');

DECLARE @Dummy TABLE (ID INT, SomeTextToSplit NVARCHAR(MAX))
INSERT INTO @Dummy VALUES
 (1,N'A&B;C;D;E, F')
,(2,N'"C" & ''D'';<C>;D;E, F');

DECLARE @Delimiter NVARCHAR(10)=';'; --special effort needed (due to entities coding with "&code;")!

WITH Casted AS
(
    SELECT *
          ,CAST(N'<x>' + REPLACE((SELECT REPLACE(SomeTextToSplit,@Delimiter,N'§§Split$me$here§§') AS [*] FOR XML PATH('')),N'§§Split$me$here§§',N'</x><x>') + N'</x>' AS XML) AS SplitMe
    FROM @Dummy
)
SELECT Casted.ID
      ,x.value(N'.',N'nvarchar(max)') AS Part 
FROM Casted
CROSS APPLY SplitMe.nodes(N'/x') AS A(x)

结果

ID  Part
1   A&B
1   C
1   D
1   E, F
2   "C" & 'D'
2   <C>
2   D
2   E, F
0赞 paparazzo 3/14/2018 #22

基于递归 cte 的解决方案

declare @T table (iden int identity, col1 varchar(100));
insert into @T(col1) values
       ('ROOT/South America/Lima/Test/Test2')
     , ('ROOT/South America/Peru/Test/Test2')
     , ('ROOT//South America/Venuzuala ')
     , ('RtT/South America / ') 
     , ('ROOT/South Americas// '); 
declare @split char(1) = '/';
select @split as split;
with cte as 
(  select t.iden, case when SUBSTRING(REVERSE(rtrim(t.col1)), 1, 1) = @split then LTRIM(RTRIM(t.col1)) else LTRIM(RTRIM(t.col1)) + @split end  as col1, 0 as pos                             , 1 as cnt
   from @T t
   union all 
   select t.iden, t.col1                                                                                                                              , charindex(@split, t.col1, t.pos + 1), cnt + 1 
   from cte t 
   where charindex(@split, t.col1, t.pos + 1) > 0 
)
select t1.*, t2.pos, t2.cnt
     , ltrim(rtrim(SUBSTRING(t1.col1, t1.pos+1, t2.pos-t1.pos-1))) as bingo
from cte t1 
join cte t2 
  on t2.iden = t1.iden 
 and t2.cnt  = t1.cnt+1
 and t2.pos > t1.pos 
order by t1.iden, t1.cnt;
2赞 alkoln 6/5/2018 #23

如果您需要一个快速的临时解决方案,用于使用最少代码的常见情况,那么这个递归 CTE 两行代码将做到这一点:

DECLARE @s VARCHAR(200) = ',1,2,,3,,,4,,,,5,'

;WITH
a AS (SELECT i=-1, j=0 UNION ALL SELECT j, CHARINDEX(',', @s, j + 1) FROM a WHERE j > i),
b AS (SELECT SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b

将其用作独立语句,或者将上述 CTE 添加到任何查询中,您将能够将生成的表与其他表联接以用于任何进一步的表达式。b

编辑(由 Shnugo 提供)

如果添加计数器,您将获得一个仓位索引和列表:

DECLARE @s VARCHAR(200) = '1,2333,344,4'

;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(',', @s, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(@s, i+1, IIF(j>0, j, LEN(@s)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;

结果:

n   s
1   1
2   2333
3   344
4   4

评论

0赞 Shnugo 8/14/2018
我喜欢这种方法。我希望你不介意,我直接在你的答案中添加了一些增强功能。请随意以任何方便的方式编辑它......
0赞 Gemunu R Wickremasinghe 6/23/2018 #24

恕我直言@AviG这是他为全额返回所有代币而设置的无错误函数版本。

IF EXISTS (SELECT * FROM sys.objects WHERE type = 'TF' AND name = 'TF_SplitString')
DROP FUNCTION [dbo].[TF_SplitString]
GO

-- =============================================
-- Author:  AviG
-- Amendments:  Parameterize the delimeter and included the missing chars in last token - Gemunu Wickremasinghe
-- Description: Tabel valued function that Breaks the delimeted string by given delimeter and returns a tabel having split results
-- Usage
-- select * from   [dbo].[TF_SplitString]('token1,token2,,,,,,,,token969',',')
-- 969 items should be returned
-- select * from   [dbo].[TF_SplitString]('4672978261,4672978255',',')
-- 2 items should be returned
-- =============================================
CREATE FUNCTION dbo.TF_SplitString 
( @stringToSplit VARCHAR(MAX) ,
  @delimeter char = ','
)
RETURNS
 @returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN

    DECLARE @name NVARCHAR(255)
    DECLARE @pos INT

    WHILE LEN(@stringToSplit) > 0
    BEGIN
        SELECT @pos  = CHARINDEX(@delimeter, @stringToSplit)


        if @pos = 0
        BEGIN
            SELECT @pos = LEN(@stringToSplit)
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos)  
        END
        else 
        BEGIN
            SELECT @name = SUBSTRING(@stringToSplit, 1, @pos-1)
        END

        INSERT INTO @returnList 
        SELECT @name

        SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos+1, LEN(@stringToSplit)-@pos)
    END

 RETURN
END
0赞 user4548445 1/26/2019 #25

这是基于安迪·罗伯逊(Andy Robertson)的回答,我需要逗号以外的分隔符。

CREATE FUNCTION dbo.splitstring ( @stringToSplit nvarchar(MAX), @delim nvarchar(max))
RETURNS
 @returnList TABLE ([value] [nvarchar] (MAX))
AS
BEGIN

 DECLARE @value NVARCHAR(max)
 DECLARE @pos INT

 WHILE CHARINDEX(@delim, @stringToSplit) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(@delim, @stringToSplit)  
  SELECT @value = SUBSTRING(@stringToSplit, 1, @pos - 1)

  INSERT INTO @returnList 
  SELECT @value

  SELECT @stringToSplit = SUBSTRING(@stringToSplit, @pos + LEN(@delim), LEN(@stringToSplit) - @pos)
 END

 INSERT INTO @returnList
 SELECT @stringToSplit

 RETURN
END
GO

并使用它:

SELECT * FROM dbo.splitstring('test1 test2 test3', ' ');

(在 SQL Server 2008 R2 上测试)

编辑:正确的测试代码

2赞 Alex 12/5/2019 #26

我通过将值包装成元素(M,但任何方法都可以)来采用xml路由:

declare @v nvarchar(max) = '100,201,abcde'

select 
    a.value('.', 'varchar(max)')
from
    (select cast('<M>' + REPLACE(@v, ',', '</M><M>') + '</M>' AS XML) as col) as A
    CROSS APPLY A.col.nodes ('/M') AS Split(a)
1赞 FelipeFalanque 3/11/2021 #27

简单

DECLARE @String varchar(100) = '11,21,84,85,87'

SELECT * FROM TB_PAPEL WHERE CD_PAPEL IN (SELECT value FROM STRING_SPLIT(@String, ','))
-- EQUIVALENTE
SELECT * FROM TB_PAPEL WHERE CD_PAPEL IN (11,21,84,85,87)