【问题标题】:Turning a Comma Separated string into individual rows将逗号分隔的字符串转换为单独的行
【发布时间】:2021-12-13 14:01:46
【问题描述】:

我有一个这样的 SQL 表:

| SomeID         | OtherID     | Data
+----------------+-------------+-------------------
| abcdef-.....   | cdef123-... | 18,20,22
| abcdef-.....   | 4554a24-... | 17,19
| 987654-.....   | 12324a2-... | 13,19,20

是否有一个查询,我可以在其中执行类似 SELECT OtherID, SplitData WHERE SomeID = 'abcdef-.......' 的查询,该查询返回单个行,如下所示:

| OtherID     | SplitData
+-------------+-------------------
| cdef123-... | 18
| cdef123-... | 20
| cdef123-... | 22
| 4554a24-... | 17
| 4554a24-... | 19

基本上将逗号处的数据拆分为单独的行?

我知道将comma-separated 字符串存储到关系数据库中听起来很愚蠢,但消费者应用程序中的正常用例确实很有帮助。

我不想在应用程序中进行拆分,因为我需要分页,所以我想在重构整个应用程序之前探索选项。

它是SQL Server 2008(非 R2)。

【问题讨论】:

标签: sql-server csv tsql split


【解决方案1】:

您可以使用 SQL Server 中出色的递归函数:


示例表:

CREATE TABLE Testdata
(
    SomeID INT,
    OtherID INT,
    String VARCHAR(MAX)
);

INSERT Testdata SELECT 1,  9, '18,20,22';
INSERT Testdata SELECT 2,  8, '17,19';
INSERT Testdata SELECT 3,  7, '13,19,20';
INSERT Testdata SELECT 4,  6, '';
INSERT Testdata SELECT 9, 11, '1,2,3,4';

查询

WITH tmp(SomeID, OtherID, DataItem, String) AS
(
    SELECT
        SomeID,
        OtherID,
        LEFT(String, CHARINDEX(',', String + ',') - 1),
        STUFF(String, 1, CHARINDEX(',', String + ','), '')
    FROM Testdata
    UNION all

    SELECT
        SomeID,
        OtherID,
        LEFT(String, CHARINDEX(',', String + ',') - 1),
        STUFF(String, 1, CHARINDEX(',', String + ','), '')
    FROM tmp
    WHERE
        String > ''
)
SELECT
    SomeID,
    OtherID,
    DataItem
FROM tmp
ORDER BY SomeID;
-- OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option

输出

 SomeID | OtherID | DataItem 
--------+---------+----------
 1      | 9       | 18       
 1      | 9       | 20       
 1      | 9       | 22       
 2      | 8       | 17       
 2      | 8       | 19       
 3      | 7       | 13       
 3      | 7       | 19       
 3      | 7       | 20       
 4      | 6       |          
 9      | 11      | 1        
 9      | 11      | 2        
 9      | 11      | 3        
 9      | 11      | 4        

【讨论】:

  • 如果将列Data的数据类型从varchar(max)更改为varchar(4000),则代码不起作用,例如create table Testdata(SomeID int, OtherID int, Data varchar(4000))?
  • @NickW 这可能是因为 UNION ALL 之前和之后的部分从 LEFT 函数返回不同的类型。就我个人而言,我不明白为什么一旦达到 4000 就不会跳到 MAX...
  • 对于一大组值,这可能会超出 CTE 的递归限制。
  • @dsz 那是你使用OPTION (maxrecursion 0)
  • LEFT 函数可能需要 CAST 才能工作....例如 LEFT(CAST(Data AS VARCHAR(MAX))....
【解决方案2】:

SQL Server 2016 终于结束了等待。他们引入了拆分字符串功能,STRING_SPLIT:

select OtherID, cs.Value --SplitData
from yourtable
cross apply STRING_SPLIT (Data, ',') cs

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

这是一篇很好的文章,有性能比较:Performance Surprises and Assumptions: STRING_SPLIT

对于旧版本,使用 tally table 这是一个拆分字符串函数(最好的方法)

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

【讨论】:

  • 如果只有服务器在 SQL Server 2016 上,我会使用 STRING_SPLIT!顺便说一句,根据您链接到的页面,它输出的字段名称是value,而不是SplitData
  • 接受的答案有效,但考虑到现在是 2021 年,这是现在应该优先考虑的答案。谢谢你 - SPLIT_STRING 正是我想要的。
【解决方案3】:

检查一下

 SELECT A.OtherID,  
     Split.a.value('.', 'VARCHAR(100)') AS Data  
 FROM  
 (
     SELECT OtherID,  
         CAST ('<M>' + REPLACE(Data, ',', '</M><M>') + '</M>' AS XML) AS Data  
     FROM  Table1
 ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a); 

【讨论】:

  • 使用这种方法时,您必须确保没有任何值包含非法 XML 的内容
  • 这很棒。请问,如果我希望新列仅显示拆分字符串中的第一个字符,我将如何重写?
  • 我必须告诉你,这种方法是“lovingl”(感受到爱了吗?),称为“XML 拆分器方法”,几乎与 While 循环或递归 CTE 一样慢。我强烈建议您始终避免使用它。请改用 DelimitedSplit8K。除了 2016 年的 Split_String() 函数或编写良好的 CLR 之外,它把所有东西都吹走了。
【解决方案4】:
select t.OtherID,x.Kod
    from testData t
    cross apply (select Code from dbo.Split(t.Data,',') ) x

【讨论】:

  • 完全符合我的要求,并且比许多其他示例更易于阅读(前提是数据库中已经有一个用于分隔字符串拆分的函数)。作为以前不熟悉CROSS APPLY 的人,这有点用处!
  • 我无法理解这部分(从 dbo.Split(t.Data,',') 中选择代码)? dbo.Split 是一个存在的表,并且 Code 是拆分表中的列?我在此页面的任何地方都找不到这些表或值的列表?
  • 我的工作代码是:select t.OtherID, x.* from testData t cross apply (select item as Data from dbo.Split(t.Data,',') ) x
【解决方案5】:

很晚,但试试这个:

SELECT ColumnID, Column1, value  --Do not change 'value' name. Leave it as it is.
FROM tbl_Sample  
CROSS APPLY STRING_SPLIT(Tags, ','); --'Tags' is the name of column containing comma separated values

所以我们有这个: tbl_Sample:

ColumnID|   Column1 |   Tags
--------|-----------|-------------
1       |   ABC     |   10,11,12    
2       |   PQR     |   20,21,22

运行此查询后:

ColumnID|   Column1 |   value
--------|-----------|-----------
1       |   ABC     |   10
1       |   ABC     |   11
1       |   ABC     |   12
2       |   PQR     |   20
2       |   PQR     |   21
2       |   PQR     |   22

谢谢!

【讨论】:

  • STRING_SPLIT 很漂亮,但它需要 SQL Server 2016。docs.microsoft.com/en-us/sql/t-sql/functions/…
  • 优雅的解决方案。
  • 是的,@SangramNandkhile 确实说过,这是最优雅的解决方案,无需声明任何变量,注释很好的代码,这就是我想要的。感谢地牢
【解决方案6】:

截至 2016 年 2 月 - 请参阅 TALLY 表示例 - 从 2014 年 2 月开始,很可能会超过我的以下 TVF。保留下面的原始帖子以供后代使用:


在上面的例子中,我喜欢的重复代码太多了。而且我不喜欢 CTE 和 XML 的性能。此外,显式的Id 以便特定于订单的消费者可以指定ORDER BY 子句。

CREATE FUNCTION dbo.Split
(
    @Line nvarchar(MAX),
    @SplitOn nvarchar(5) = ','
)
RETURNS @RtnValue table
(
    Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    Data nvarchar(100) NOT NULL
)
AS
BEGIN
    IF @Line IS NULL RETURN;

    DECLARE @split_on_len INT = LEN(@SplitOn);
    DECLARE @start_at INT = 1;
    DECLARE @end_at INT;
    DECLARE @data_len INT;

    WHILE 1=1
    BEGIN
        SET @end_at = CHARINDEX(@SplitOn,@Line,@start_at);
        SET @data_len = CASE @end_at WHEN 0 THEN LEN(@Line) ELSE @end_at-@start_at END;
        INSERT INTO @RtnValue (data) VALUES( SUBSTRING(@Line,@start_at,@data_len) );
        IF @end_at = 0 BREAK;
        SET @start_at = @end_at + @split_on_len;
    END;

    RETURN;
END;

【讨论】:

    【解决方案7】:

    很高兴看到它已在 2016 版本中解决,但对于所有未解决的问题,这里有上述方法的两个通用和简化版本。

    XML 方法更短,但当然需要字符串以允许 xml 技巧(没有“坏”字符。)

    XML 方法:

    create function dbo.splitString(@input Varchar(max), @Splitter VarChar(99)) returns table as
    Return
        SELECT Split.a.value('.', 'VARCHAR(max)') AS Data FROM
        ( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data 
        ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a); 
    

    递归方法:

    create function dbo.splitString(@input Varchar(max), @Splitter Varchar(99)) returns table as
    Return
      with tmp (DataItem, ix) as
       ( select @input  , CHARINDEX('',@Input)  --Recu. start, ignored val to get the types right
         union all
         select Substring(@input, ix+1,ix2-ix-1), ix2
         from (Select *, CHARINDEX(@Splitter,@Input+@Splitter,ix+1) ix2 from tmp) x where ix2<>0
       ) select DataItem from tmp where ix<>0
    

    实际作用

    Create table TEST_X (A int, CSV Varchar(100));
    Insert into test_x select 1, 'A,B';
    Insert into test_x select 2, 'C,D';
    
    Select A,data from TEST_X x cross apply dbo.splitString(x.CSV,',') Y;
    
    Drop table TEST_X
    

    XML-METHOD 2:Unicode 友好 ?(由 Max Hodges 提供) create function dbo.splitString(@input nVarchar(max), @Splitter nVarchar(99)) returns table as Return SELECT Split.a.value('.', 'NVARCHAR(max)') AS Data FROM ( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);

    【讨论】:

    • 这似乎很明显,但是你如何使用这两个函数呢?特别是,你能展示如何在 OP 的用例中使用它吗?
    • 这里是一个简单的例子: 创建表 TEST_X (A int, CSV Varchar(100));插入 test_x 选择 1, 'A,B';插入 test_x 选择 2, 'C,D'; Select A,data from TEST_X x cross apply dbo.splitString(x.CSV,',') Y;删除表 TEST_X
    • 这正是我所需要的!谢谢。
    【解决方案8】:

    请参考下面的 TSQL。 STRING_SPLIT 函数仅在兼容级别 130 及以上可用。

    TSQL:

    DECLARE @stringValue NVARCHAR(400) = 'red,blue,green,yellow,black';
    DECLARE @separator CHAR = ',';
    
    SELECT [value]  As Colour
    FROM STRING_SPLIT(@stringValue, @separator); 
    

    结果:

    颜色

    红色 蓝色 绿色 黄色的 黑色

    【讨论】:

      【解决方案9】:

      我知道它有很多答案,但我想像其他人一样编写我的 split 函数版本以及 string_split SQL Server 2016 原生函数。

      create function [dbo].[Split]
      (
          @Value nvarchar(max),
          @Delimiter nvarchar(50)
      )
      returns @tbl table
      (
          Seq int primary key identity(1, 1),
          Value nvarchar(max)
      )
      as begin
          declare @Xml xml = cast('<d>' + replace(@Value, @Delimiter, '</d><d>') + '</d>' as xml);
      
          insert into @tbl
                  (Value)
          select  a.split.value('.', 'nvarchar(max)') as Value
          from    @Xml.nodes('/d') a(split);
          
          return;
      end;
      
      • Seq 列为主键,支持与其他真实表或Split 函数返回表的快速连接。
      • 使用XML函数支持大数据(大数据循环版本会明显变慢)

      这是问题的答案。

      CREATE TABLE Testdata
      (
          SomeID INT,
          OtherID INT,
          String VARCHAR(MAX)
      );
      
      INSERT Testdata SELECT 1,  9, '18,20,22';
      INSERT Testdata SELECT 2,  8, '17,19';
      INSERT Testdata SELECT 3,  7, '13,19,20';
      INSERT Testdata SELECT 4,  6, '';
      INSERT Testdata SELECT 9, 11, '1,2,3,4';
      
      
      select  t.SomeID, t.OtherID, s.Value
      from    Testdata t
              cross apply dbo.Split(t.String, ',') s;
      
      --Output
      SomeID  OtherID Value
      1       9       18
      1       9       20
      1       9       22
      2       8       17
      2       8       19
      3       7       13
      3       7       19
      3       7       20
      4       6       
      9       11      1
      9       11      2
      9       11      3
      9       11      4
      

      与其他拆分加入拆分

      declare @Names nvarchar(max) = 'a,b,c,d';
      declare @Codes nvarchar(max) = '10,20,30,40';
      
      select  n.Seq, n.Value Name, c.Value Code
      from    dbo.Split(@Names, ',') n
              inner join dbo.Split(@Codes, ',') c on n.Seq = c.Seq;
      
      --Output
      Seq Name    Code
      1   a       10
      2   b       20
      3   c       30
      4   d       40
      

      分两次

      declare @NationLocSex nvarchar(max) = 'Korea,Seoul,1;Vietnam,Kiengiang,0;China,Xian,0';
      
      with rows as
      (
          select  Value
          from    dbo.Split(@NationLocSex, ';')
      )
      select  rw.Value r, cl.Value c
      from    rows rw
              cross apply dbo.Split(rw.Value, ',') cl;
      
      --Output
      r                       c
      Korea,Seoul,1           Korea
      Korea,Seoul,1           Seoul
      Korea,Seoul,1           1
      Vietnam,Kiengiang,0     Vietnam
      Vietnam,Kiengiang,0     Kiengiang
      Vietnam,Kiengiang,0     0
      China,Xian,0            China
      China,Xian,0            Xian
      China,Xian,0            0
      

      分列

      declare @Numbers nvarchar(50) = 'First,Second,Third';
      
      with t as
      (
          select  case when Seq = 1 then Value end f1,
                  case when Seq = 2 then Value end f2,
                  case when Seq = 3 then Value end f3
          from    dbo.Split(@Numbers, ',')
      )
      select  min(f1) f1, min(f2) f2, min(f3) f3
      from    t;
      
      --Output
      f1      f2      f3
      First   Second  Third
      

      按范围生成行

      
      declare @Ranges nvarchar(50) = '1-2,4-6';
      
      declare @Numbers table (Num int);
      insert into @Numbers values (1),(2),(3),(4),(5),(6),(7),(8);
      
      with t as
      (
          select  r.Seq, r.Value,
                  min(case when ft.Seq = 1 then ft.Value end) ValueFrom,
                  min(case when ft.Seq = 2 then ft.Value end) ValueTo
          from    dbo.Split(@Ranges, ',') r
                  cross apply dbo.Split(r.Value, '-') ft
          group by r.Seq, r.Value
      )
      select  t.Seq, t.Value, t.ValueFrom, t.ValueTo, n.Num
      from    t
              inner join @Numbers n on n.Num between t.ValueFrom and t.ValueTo;
      
      --Output
      Seq Value   ValueFrom   ValueTo Num
      1   1-2     1           2       1
      1   1-2     1           2       2
      2   4-6     4           6       4
      2   4-6     4           6       5
      2   4-6     4           6       6
      

      【讨论】:

        【解决方案10】:
        DECLARE @id_list VARCHAR(MAX) = '1234,23,56,576,1231,567,122,87876,57553,1216';
        DECLARE @table TABLE ( id VARCHAR(50) );
        DECLARE @x INT = 0;
        DECLARE @firstcomma INT = 0;
        DECLARE @nextcomma INT = 0;
        
        SET @x = LEN(@id_list) - LEN(REPLACE(@id_list, ',', '')) + 1; -- number of ids in id_list
        
        WHILE @x > 0
            BEGIN
                SET @nextcomma = CASE WHEN CHARINDEX(',', @id_list, @firstcomma + 1) = 0
                                      THEN LEN(@id_list) + 1
                                      ELSE CHARINDEX(',', @id_list, @firstcomma + 1)
                                 END;
                INSERT  INTO @table
                VALUES  ( SUBSTRING(@id_list, @firstcomma + 1, (@nextcomma - @firstcomma) - 1) );
                SET @firstcomma = CHARINDEX(',', @id_list, @firstcomma + 1);
                SET @x = @x - 1;
            END;
        
        SELECT  *
        FROM    @table;
        

        【讨论】:

        • 这是少数几种适用于 Azure SQL 数据仓库中有限 SQL 支持的方法之一。
        【解决方案11】:
        ;WITH tmp(SomeID, OtherID, DataItem, Data) as (
            SELECT SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
                STUFF(Data, 1, CHARINDEX(',',Data+','), '')
        FROM Testdata
        WHERE Data > ''
        )
        SELECT SomeID, OtherID, Data
        FROM tmp
        ORDER BY SomeID
        

        只对上面的查询进行微小的修改...

        【讨论】:

        • 您能否简要解释一下这是对已接受答案中版本的改进?
        • 没有联合所有...更少的代码。既然是用union all而不是union,不应该是性能上的区别吗?
        • 这并没有返回它应该有的所有行。我不确定数据需要全部联合,但您的解决方案返回的行数与原始表相同。
        • (这里的问题是递归部分被省略了……)
        • 不给我预期的输出只在单独的行中给出第一条记录
        【解决方案12】:

        通过创建分割字符串的函数 ([DelimitedSplit]),您可以对 SELECT 执行 OUTER APPLY。

        CREATE FUNCTION [dbo].[DelimitedSplit]
        --===== Define I/O parameters
                (@pString VARCHAR(8000), @pDelimiter CHAR(1))
        --WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
        RETURNS TABLE WITH SCHEMABINDING AS
         RETURN
        --===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
             -- enough to cover VARCHAR(8000)
          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 INNER JOIN E1 b ON b.N = a.N), --10E+2 or 100 rows
               E4(N) AS (SELECT 1 FROM E2 a INNER JOIN E2 b ON b.N = a.N), --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
        ;
        

        测试

        CREATE TABLE #Testdata
        (
            SomeID INT,
            OtherID INT,
            String VARCHAR(MAX)
        );
        
        INSERT #Testdata SELECT 1,  9, '18,20,22';
        INSERT #Testdata SELECT 2,  8, '17,19';
        INSERT #Testdata SELECT 3,  7, '13,19,20';
        INSERT #Testdata SELECT 4,  6, '';
        INSERT #Testdata SELECT 9, 11, '1,2,3,4';
        
        SELECT
         *
        FROM #Testdata
        OUTER APPLY [dbo].[DelimitedSplit](String,',');
        
        DROP TABLE #Testdata;
        

        结果

        SomeID  OtherID String      ItemNumber  Item
        1       9       18,20,22    1           18
        1       9       18,20,22    2           20
        1       9       18,20,22    3           22
        2       8       17,19       1           17
        2       8       17,19       2           19
        3       7       13,19,20    1           13
        3       7       13,19,20    2           19
        3       7       13,19,20    3           20
        4       6       1   
        9       11      1,2,3,4     1           1
        9       11      1,2,3,4     2           2
        9       11      1,2,3,4     3           3
        9       11      1,2,3,4     4           4
        

        【讨论】:

          【解决方案13】:

          功能

          CREATE FUNCTION dbo.SplitToRows (@column varchar(100), @separator varchar(10))
          RETURNS @rtnTable TABLE
            (
            ID int identity(1,1),
            ColumnA varchar(max)
            )
           AS
          BEGIN
              DECLARE @position int = 0;
              DECLARE @endAt int = 0;
              DECLARE @tempString varchar(100);
              
              set @column = ltrim(rtrim(@column));
          
              WHILE @position<=len(@column)
              BEGIN       
                  set @endAt = CHARINDEX(@separator,@column,@position);
                      if(@endAt=0)
                      begin
                      Insert into @rtnTable(ColumnA) Select substring(@column,@position,len(@column)-@position);
                      break;
                      end;
                  set @tempString = substring(ltrim(rtrim(@column)),@position,@endAt-@position);
          
                  Insert into @rtnTable(ColumnA) select @tempString;
                  set @position=@endAt+1;
              END;
              return;
          END;
          

          用例

          select * from dbo.SplitToRows('T14; p226.0001; eee; 3554;', ';');
          

          或者只是一个带有多个结果集的选择

          DECLARE @column varchar(max)= '1234; 4748;abcde; 324432';
          DECLARE @separator varchar(10) = ';';
          DECLARE @position int = 0;
          DECLARE @endAt int = 0;
          DECLARE @tempString varchar(100);
          
          set @column = ltrim(rtrim(@column));
          
          WHILE @position<=len(@column)
          BEGIN       
              set @endAt = CHARINDEX(@separator,@column,@position);
                  if(@endAt=0)
                  begin
                  Select substring(@column,@position,len(@column)-@position);
                  break;
                  end;
              set @tempString = substring(ltrim(rtrim(@column)),@position,@endAt-@position);
          
              select @tempString;
              set @position=@endAt+1;
          END;
          

          【讨论】:

          • 在多语句表值函数中使用 while 循环几乎是拆分字符串的最糟糕方法。这个问题已经有很多基于集合的选项了。
          【解决方案14】:

          使用这种方法时,您必须确保您的值不包含非法 XML 的内容 - user1151923

          我总是使用 XML 方法。确保您使用有效的 XML。我有两个函数可以在有效的 XML 和文本之间进行转换。 (我倾向于去掉回车,因为我通常不需要它们。

          CREATE FUNCTION dbo.udf_ConvertTextToXML (@Text varchar(MAX)) 
              RETURNS varchar(MAX)
          AS
              BEGIN
                  SET @Text = REPLACE(@Text,CHAR(10),'');
                  SET @Text = REPLACE(@Text,CHAR(13),'');
                  SET @Text = REPLACE(@Text,'<','&lt;');
                  SET @Text = REPLACE(@Text,'&','&amp;');
                  SET @Text = REPLACE(@Text,'>','&gt;');
                  SET @Text = REPLACE(@Text,'''','&apos;');
                  SET @Text = REPLACE(@Text,'"','&quot;');
              RETURN @Text;
          END;
          
          
          CREATE FUNCTION dbo.udf_ConvertTextFromXML (@Text VARCHAR(MAX)) 
              RETURNS VARCHAR(max)
          AS
              BEGIN
                  SET @Text = REPLACE(@Text,'&lt;','<');
                  SET @Text = REPLACE(@Text,'&amp;','&');
                  SET @Text = REPLACE(@Text,'&gt;','>');
                  SET @Text = REPLACE(@Text,'&apos;','''');
                  SET @Text = REPLACE(@Text,'&quot;','"');
              RETURN @Text;
          END;
          

          【讨论】:

          • 那里的代码有一个小问题。它会将 '
          • 不需要这样的功能...只要使用隐含的能力。试试这个:SELECT (SELECT '&lt;&amp;&gt; blah' + CHAR(13)+CHAR(10) + 'next line' FOR XML PATH(''))
          【解决方案15】:

          以下适用于 sql server 2008

          select *, ROW_NUMBER() OVER(order by items) as row# 
          from 
          ( select 134 myColumn1, 34 myColumn2, 'd,c,k,e,f,g,h,a' comaSeperatedColumn) myTable
              cross apply 
          SPLIT (rtrim(comaSeperatedColumn), ',') splitedTable -- gives 'items'  column 
          

          将获得所有带有原始表列的笛卡尔积加上拆分表的“项目”。

          【讨论】:

            【解决方案16】:

            您可以使用以下函数来提取数据

            CREATE FUNCTION [dbo].[SplitString]
            (    
                @RowData NVARCHAR(MAX),
                @Delimeter NVARCHAR(MAX)
            )
            RETURNS @RtnValue TABLE 
            (
                ID INT IDENTITY(1,1),
                Data NVARCHAR(MAX)
            ) 
            AS
            BEGIN 
                DECLARE @Iterator INT;
                SET @Iterator = 1;
            
                DECLARE @FoundIndex INT;
                SET @FoundIndex = CHARINDEX(@Delimeter,@RowData);
            
                WHILE (@FoundIndex>0)
                BEGIN
                    INSERT INTO @RtnValue (data)
                    SELECT 
                        Data = LTRIM(RTRIM(SUBSTRING(@RowData, 1, @FoundIndex - 1)));
            
                    SET @RowData = SUBSTRING(@RowData,
                            @FoundIndex + DATALENGTH(@Delimeter) / 2,
                            LEN(@RowData));
            
                    SET @Iterator = @Iterator + 1;
                    SET @FoundIndex = CHARINDEX(@Delimeter, @RowData);
                END;
                
                INSERT INTO @RtnValue (Data)
                SELECT Data = LTRIM(RTRIM(@RowData));
            
                RETURN;
            END;
            

            【讨论】:

            • 在多语句表值函数中使用 while 循环几乎是拆分字符串的最糟糕方法。这个问题已经有很多基于集合的选项了。
            猜你喜欢
            • 2015-05-20
            • 1970-01-01
            • 2012-02-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多