Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/85.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在SQL server中将字符串拆分为给定行分隔符和列分隔符的表_Sql_Sql Server_String_Split - Fatal编程技术网

在SQL server中将字符串拆分为给定行分隔符和列分隔符的表

在SQL server中将字符串拆分为给定行分隔符和列分隔符的表,sql,sql-server,string,split,Sql,Sql Server,String,Split,如何在SQL Server中将包含矩阵的字符串拆分为表?字符串包含列和行分隔符 假设我有一个字符串: declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z'; 预期结果(分为三列): 我正在寻找一个通用的解决方案,它没有定义列和行的数量。因此,字符串: declare @str varchar(max)='A,B;D,E'; 将拆分为具有两列的表: +---+---+ | A | B | +---+---+ | D | E | +---+---+ 我的努

如何在SQL Server中将包含矩阵的字符串拆分为表?字符串包含列和行分隔符

假设我有一个字符串:

declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z';
预期结果(分为三列):


我正在寻找一个通用的解决方案,它没有定义列和行的数量。因此,字符串:

declare @str varchar(max)='A,B;D,E';
将拆分为具有两列的表:

+---+---+
| A | B |
+---+---+
| D | E |
+---+---+
我的努力。我的第一个想法是使用动态SQL,将字符串转换为:
insert into dbo.temp values(…)
这种方法虽然非常快,但有一个小缺点,因为它需要先创建一个列数正确的表。我提出这个方法只是为了使问题简短

另一种方法是将字符串写入服务器上的CSV文件,然后从中批量插入。虽然我不知道如何做到这一点,以及第一个和第二个想法的表现如何

我问这个问题的原因是因为我想将数据从Excel导入SQL Server。正如我用不同的ADO方法所做的实验一样,这种发送矩阵字符串的方法是压倒性的胜利,特别是当字符串的长度增加时。我在这里问了一个双胞胎弟弟的问题:在哪里可以找到如何从Excel range中准备这样一个字符串的建议


赏金我决定奖励马特。我高度评价了肖恩·兰格的回答。谢谢你,肖恩。我喜欢马特的答案,因为它简单明了。除了Matt和Sean的不同方法可以并行使用,因此目前我不接受任何答案(更新:最后,几个月后,我接受了Matt的答案)。我要感谢艾哈迈德·赛义德(Ahmed Saeed)的价值观理念,因为这是我开始时答案的一个很好的演变。当然,这不是马特和肖恩的对手。我对每个答案都投了赞成票。如果您对使用这些方法有任何反馈,我将不胜感激。谢谢你的追求

一种更简单的方法是根据替换分隔符将字符串转换为XML

declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z';
DECLARE @xmlstr XML
SET @xmlstr = CAST(('<rows><row><col>' + REPLACE(REPLACE(@str,';','</col></row><row><col>'),',','</col><col>') + '</col></row></rows>') AS XML)

SELECT
    t.n.value('col[1]','CHAR(1)') as Col1
    ,t.n.value('col[2]','CHAR(1)') as Col2
    ,t.n.value('col[3]','CHAR(1)') as Col3
FROM
    @xmlstr.nodes ('/rows/row') AS t(n)

下面是一种通过使用
Split
自定义函数的动态
PIVOT
执行此操作的方法:

分割功能

CREATE FUNCTION [dbo].[fn_Split](@text varchar(MAX), @delimiter varchar(20) = ' ')
RETURNS @Strings TABLE
(    
  position int IDENTITY PRIMARY KEY,
  value varchar(MAX)   
)
AS
BEGIN

DECLARE @index int 
SET @index = -1 

WHILE (LEN(@text) > 0) 
  BEGIN  
    SET @index = CHARINDEX(@delimiter , @text)  
    IF (@index = 0) AND (LEN(@text) > 0)  
      BEGIN   
        INSERT INTO @Strings VALUES (@text)
          BREAK  
      END  
    IF (@index > 1)  
      BEGIN   
        INSERT INTO @Strings VALUES (LEFT(@text, @index - 1))   
        SET @text = RIGHT(@text, (LEN(@text) - @index))  
      END  
    ELSE 
      SET @text = RIGHT(@text, (LEN(@text) - @index)) 
    END
  RETURN
END

GO
查询

Declare @Str Varchar (Max) = 'A,B,C;D,E,F;X,Y,Z';
Declare @Sql NVarchar (Max) = '',
        @Cols NVarchar (Max) = '';

;With Rows As
(
    Select      Position, Value As Row
    From        dbo.fn_Split(@str, ';')
), Columns As
(
    Select      Rows.Position   As RowNum,
                Cols.Position   As ColNum,
                Cols.Value      As ColValue 
    From        Rows
    Cross Apply dbo.fn_Split(Row, ',') Cols
)
Select  *
Into    #Columns
From    Columns

Select  @Cols = Stuff(( Select  Distinct ',' + QuoteName(ColNum)
                        From    #Columns
                        For Xml Path(''), Type).value('.', 'NVARCHAR(MAX)')
                    , 1, 1, '')

Select  @SQL = 'SELECT ' + @Cols + ' FROM #Columns 
Pivot 
(
    Max(ColValue)
    For ColNum In (' + @Cols + ')
) P
Order By RowNum'

Execute (@SQL)
结果

1   2   3
A   B   C
D   E   F
X   Y   Z
**--使用动态查询**
声明@str varchar(max)='A、B、C;D、 E,F;十、 Y,Z′;
声明@cc int
选择@cc=len(子字符串(@str,0,charindex(“;”,@str))-len(替换(子字符串(@str,0,charindex(“;”,@str)),”,“,”)
声明@ctq varchar(max)='create table t('
声明@i int=0

而@i我发布问题的答案只是为了扩展问题,以显示我在提问时使用了什么

想法是将原始字符串更改为:

insert into dbo.temp values (...)(...)
下面是一个存储过程:

create PROC [dbo].[StringToMatrix] 
(
 @String nvarchar(max)
,@DelimiterCol nvarchar(50)=','
,@DelimiterRow nvarchar(50)=';'
,@InsertTable nvarchar(200) ='dbo.temp'
,@Delete int=1 --delete is ON
) 
AS
BEGIN
set nocount on;

set @String = case when right(@String,len(@DelimiterRow))=@DelimiterRow then left(@string,len(@String)-len(@DelimiterRow)) else @String end --if present, removes the last row delimiter at the very end of string
set @String = replace(@String,@DelimiterCol,''',''')
set @String = replace(@String,@DelimiterRow,'''),'+char(13)+char(10)+'(''')   
set @String = 'insert into '+@InsertTable+' values '+char(13)+char(10)+'(''' +@String +''');'
set @String = replace(@String,'''''','null') --optional, changes empty strings to nulls

set @String = CASE 
    WHEN @Delete = 1 THEN 'delete from '+@InsertTable+';'+char(13)+char(10)+@String 
    ELSE @String 
    END

--print @String
exec (@String)
END
使用以下代码执行proc:

exec [dbo].[StringToMatrix] 'A,B,C;D,E,F;X,Y,Z'
生成以下@String:

delete from [dbo].[temp];
insert into [dbo].[temp] values 
('A','B','C'),
('D','E','F'),
('X','Y','Z');
在proc的最后一行中,它是动态执行的


该解决方案需要首先创建适当的
dbo.table
,然后将值插入其中。这是一个小缺点。因此,该解决方案的动态性不如它的结构:
select*into dbo.temp
。不过,我想与大家分享这个解决方案,因为它可以工作,速度快,简单,而且可能会很快这是一些其他答案的灵感。

好吧,这个谜题让我很感兴趣,所以我决定看看我是否可以在没有任何循环的情况下完成这项工作。这项工作有几个先决条件。首先,我们假设你有某种计数表。如果你没有,这是我的代码。我在我使用的每个系统上都保留了这个

create View [dbo].[cteTally] as

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    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 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
    )
select N from cteTally
这个难题的第二部分是需要一个基于集合的字符串拆分器。我更喜欢使用uber fast Jeff Moden拆分器。需要注意的是,它最多只能处理8000个varchar值。这对于我使用的大多数分隔字符串来说已经足够了。你可以在这里找到Jeff Moden的拆分器(DelimitedSplit8K)

最后但并非最不重要的是,我在这里使用的技术是一个动态交叉表。这是我从杰夫·摩登那里学到的其他东西。他在这里有一篇关于这个主题的伟大文章

把所有这些放在一起,你可以想出像这样的东西,这将是非常快,将规模很好

declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z';

declare @StaticPortion nvarchar(2000) = 
'declare @str varchar(max)=''' + @str + ''';with OrderedResults as
    (
        select s.ItemNumber
            , s.Item as DelimitedValues
            , x.ItemNumber as RowNum
            , x.Item
        from dbo.DelimitedSplit8K(@str, '';'') s
        cross apply dbo.DelimitedSplit8K(s.Item, '','') x
    )
    select '

declare @DynamicPortion nvarchar(max) = '';
declare @FinalStaticPortion nvarchar(2000) = ' from OrderedResults group by ItemNumber';

select @DynamicPortion = @DynamicPortion + 
    ', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Item end) as Column' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <= (select MAX(len(Item) - LEN(replace(Item, ',', ''))) + 1
                from dbo.DelimitedSplit8K(@str, ';')
            )

declare @SqlToExecute nvarchar(max) = @StaticPortion + stuff(@DynamicPortion, 1, 1, '') + @FinalStaticPortion
exec sp_executesql @SqlToExecute

这是另一种方法

Declare @Str varchar(max)='A,B,C;D,E,F;X,Y,Z';

Select A.*,B.*
 Into  #TempSplit
 From (Select RowNr=RetSeq,String=RetVal From [dbo].[udf-Str-Parse](@Str,';')) A
 Cross Apply [dbo].[udf-Str-Parse](A.String,',') B

Declare @SQL varchar(max) = ''
Select @SQL = @SQL+Concat(',Col',RetSeq,'=max(IIF(RetSeq=',RetSeq,',RetVal,null))') 
 From  (Select Distinct RetSeq from #TempSplit) A 
 Order By A.RetSeq

Set @SQL ='
If Object_ID(''[dbo].[Temp]'', ''U'') IS NOT NULL 
  Drop Table [dbo].[Temp]; 

Select ' + Stuff(@SQL,1,1,'') + ' Into [dbo].[Temp] From #TempSplit  Group By RowNr Order By RowNr 
'
Exec(@SQL)

Select * from Temp
返回

Col1    Col2    Col3
A       B       C
D       E       F
X       Y       Z
Col1    Col2    Col3
A       B       C
D       E       F
X       Y       Z
现在,这确实需要下面列出的解析器:

CREATE FUNCTION [dbo].[udf-Str-Parse] (@String varchar(max),@Delimiter varchar(10))
Returns Table 
As
Return (  
    Select RetSeq = Row_Number() over (Order By (Select null))
          ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
    From (Select x = Cast('<x>'+ Replace(@String,@Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A 
    Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
然后通过交叉应用再次解析,该交叉应用返回以下内容并存储在临时表中

RowNr   String  RetSeq  RetVal
1       A,B,C   1       A
1       A,B,C   2       B
1       A,B,C   3       C
2       D,E,F   1       D
2       D,E,F   2       E
2       D,E,F   3       F
3       X,Y,Z   1       X
3       X,Y,Z   2       Y
3       X,Y,Z   3       Z
编辑:或只是为了好玩

CREATE FUNCTION [dbo].[fn_Split](@text varchar(MAX), @delimiter varchar(20) = ' ')
RETURNS @Strings TABLE
(    
  position int IDENTITY PRIMARY KEY,
  value varchar(MAX)   
)
AS
BEGIN

DECLARE @index int 
SET @index = -1 

WHILE (LEN(@text) > 0) 
  BEGIN  
    SET @index = CHARINDEX(@delimiter , @text)  
    IF (@index = 0) AND (LEN(@text) > 0)  
      BEGIN   
        INSERT INTO @Strings VALUES (@text)
          BREAK  
      END  
    IF (@index > 1)  
      BEGIN   
        INSERT INTO @Strings VALUES (LEFT(@text, @index - 1))   
        SET @text = RIGHT(@text, (LEN(@text) - @index))  
      END  
    ELSE 
      SET @text = RIGHT(@text, (LEN(@text) - @index)) 
    END
  RETURN
END

GO

无需临时表、视图、循环或xml即可解决此问题。首先,您可以根据理货表创建字符串拆分器函数,如下例所示:

ALTER FUNCTION [dbo].[SplitString]
(
   @delimitedString VARCHAR(MAX),
   @delimiter VARCHAR(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(@delimitedString,1))) 
                         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
       cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                         WHERE (SUBSTRING(@delimitedString,t.N,1) = @delimiter OR t.N = 0))
  SELECT  ROW_NUMBER() OVER (ORDER BY s.N1) AS Nr
         ,Item = SUBSTRING(@delimitedString, s.N1, ISNULL(NULLIF(CHARINDEX(@delimiter,@delimitedString,s.N1),0)-s.N1,8000))
    FROM cteStart s;
DECLARE @source VARCHAR(max) = 'A1,B1,C1,D1,E1,F1,G1;A2,B2,C2,D2,E2,F2,G2;A3,B3,C3,D3,E3,F3,G3;A4,B4,C4,D4,E4,F4,G4;A5,B5,C5,D5,E5,F5,G5;A6,B6,C6,D6,E6,F6,G6;A7,B7,C7,D7,E7,F7,G7;A8,B8,C8,D8,E8,F8,G8;A9,B9,C9,D9,E9,F9,G9;A10,B10,C10,D10,E10,F10,G10;A11,B11,C11,D11,E11,F11,G11;A12,B12,C12,D12,E12,F12,G12;A13,B13,C13,D13,E13,F13,G13;A14,B14,C14,D14,E14,F14,G14;A15,B15,C15,D15,E15,F15,G15;A16,B16,C16,D16,E16,F16,G16;A17,B17,C17,D17,E17,F17,G17;A18,B18,C18,D18,E18,F18,G18;A19,B19,C19,D19,E19,F19,G19;A20,B20,C20,D20,E20,F20,G20'

-- First determine the columns names. Since the string can be potential very long we don’t want to parse the entire string to determine how many columns 
-- we have, instead get sub string of main string up to first row delimiter.
DECLARE @firstRow VARCHAR(max) = LEFT(@source, CHARINDEX(';', @source) - 1);
DECLARE @columnNames NVARCHAR(MAX) = '';

-- Use string splitter function on sub string to determine column names.
SELECT @columnNames = STUFF(( 
                                SELECT ',' + QUOTENAME(CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS VARCHAR(10)))
                                FROM        [dbo].[SplitString](@firstRow, ',') Items
                                FOR XML PATH('')), 1, 1, '');

-- Next build dynamic query that will generate our matrix table.
-- CTE first split string by row delimiters then it applies the string split function again on each row.  
DECLARE @pivotQuery NVARCHAR(MAX) ='
;WITH CTE_SplitData AS
(
SELECT       R.Nr AS [Row]
            ,C.[Columns]
            ,ROW_NUMBER() OVER (PARTITION BY R.Nr ORDER BY R.Item) AS ColumnNr
FROM        [dbo].[SplitString](@source, '';'') R
OUTER APPLY (
                SELECT  Item AS [Columns]
                FROM    [dbo].[SplitString](R.Item, '','') 
            ) C
)
-- Pivoted reuslt
SELECT * FROM
(  
     SELECT * 
     FROM   CTE_SplitData
)as T
PIVOT 
(
     max(T.[Columns])
     for T.[ColumnNr] in (' +  @columnNames + ')
) as P'


EXEC sp_executesql  @pivotQuery,
          N'@source VARCHAR(MAX)',  
          @source = @source;        -- Pass the source string to be split as a parameter to the dynamic query.
然后使用拆分器函数首先根据行分隔符拆分字符串。然后使用外部apply语句在每行上再次应用拆分器函数。最后透视结果。 由于列数未知,因此查询必须作为动态SQL执行,如下例所示:

ALTER FUNCTION [dbo].[SplitString]
(
   @delimitedString VARCHAR(MAX),
   @delimiter VARCHAR(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(@delimitedString,1))) 
                         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
       cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                         WHERE (SUBSTRING(@delimitedString,t.N,1) = @delimiter OR t.N = 0))
  SELECT  ROW_NUMBER() OVER (ORDER BY s.N1) AS Nr
         ,Item = SUBSTRING(@delimitedString, s.N1, ISNULL(NULLIF(CHARINDEX(@delimiter,@delimitedString,s.N1),0)-s.N1,8000))
    FROM cteStart s;
DECLARE @source VARCHAR(max) = 'A1,B1,C1,D1,E1,F1,G1;A2,B2,C2,D2,E2,F2,G2;A3,B3,C3,D3,E3,F3,G3;A4,B4,C4,D4,E4,F4,G4;A5,B5,C5,D5,E5,F5,G5;A6,B6,C6,D6,E6,F6,G6;A7,B7,C7,D7,E7,F7,G7;A8,B8,C8,D8,E8,F8,G8;A9,B9,C9,D9,E9,F9,G9;A10,B10,C10,D10,E10,F10,G10;A11,B11,C11,D11,E11,F11,G11;A12,B12,C12,D12,E12,F12,G12;A13,B13,C13,D13,E13,F13,G13;A14,B14,C14,D14,E14,F14,G14;A15,B15,C15,D15,E15,F15,G15;A16,B16,C16,D16,E16,F16,G16;A17,B17,C17,D17,E17,F17,G17;A18,B18,C18,D18,E18,F18,G18;A19,B19,C19,D19,E19,F19,G19;A20,B20,C20,D20,E20,F20,G20'

-- First determine the columns names. Since the string can be potential very long we don’t want to parse the entire string to determine how many columns 
-- we have, instead get sub string of main string up to first row delimiter.
DECLARE @firstRow VARCHAR(max) = LEFT(@source, CHARINDEX(';', @source) - 1);
DECLARE @columnNames NVARCHAR(MAX) = '';

-- Use string splitter function on sub string to determine column names.
SELECT @columnNames = STUFF(( 
                                SELECT ',' + QUOTENAME(CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS VARCHAR(10)))
                                FROM        [dbo].[SplitString](@firstRow, ',') Items
                                FOR XML PATH('')), 1, 1, '');

-- Next build dynamic query that will generate our matrix table.
-- CTE first split string by row delimiters then it applies the string split function again on each row.  
DECLARE @pivotQuery NVARCHAR(MAX) ='
;WITH CTE_SplitData AS
(
SELECT       R.Nr AS [Row]
            ,C.[Columns]
            ,ROW_NUMBER() OVER (PARTITION BY R.Nr ORDER BY R.Item) AS ColumnNr
FROM        [dbo].[SplitString](@source, '';'') R
OUTER APPLY (
                SELECT  Item AS [Columns]
                FROM    [dbo].[SplitString](R.Item, '','') 
            ) C
)
-- Pivoted reuslt
SELECT * FROM
(  
     SELECT * 
     FROM   CTE_SplitData
)as T
PIVOT 
(
     max(T.[Columns])
     for T.[ColumnNr] in (' +  @columnNames + ')
) as P'


EXEC sp_executesql  @pivotQuery,
          N'@source VARCHAR(MAX)',  
          @source = @source;        -- Pass the source string to be split as a parameter to the dynamic query.

一些带有数据透视和动态SQL的XML

  • 替换为标记
    p
    ,将其转换为XML

  • 然后计算列数并将其放入
    @i

  • 使用
    colsPiv
    CTE,我们生成一个字符串并将其放入
    @col
    ,字符串类似于
    ,[1],[2],…[n]
    它将用于旋转

  • 然后我们生成一个动态透视查询并执行它。我们还传递了两个参数XML和列计数

  • 以下是查询:

    --declare @str varchar(max)='A,B;D,E;X,Y',
    declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z',
            @x xml,
            @col nvarchar(max),
            @sql nvarchar(max),
            @params nvarchar(max) = '@x xml, @i int',
            @i int
    
    SELECT  @x = CAST('<row>'+REPLACE(('<p>'+REPLACE(@str,',','</p><p>')+'</p>'),';','</p></row><row><p>')+'</row>' as xml),
            @str = REPLACE(@str,';',',;')+',;', 
            @i =  (LEN(@str)-LEN(REPLACE(@str,',','')))/(LEN(@str)-LEN(REPLACE(@str,';','')))
    
    ;WITH colsPiv AS (
        SELECT 1 as col
        UNION ALL
        SELECT col+1
        FROM colsPiv
        WHERE col < @i
    )
    
    SELECT @col = (
        SELECT ','+QUOTENAME(col)
        FROM colsPiv
        FOR XML PATH('')
    )
    
    SELECT @sql = N'
    ;WITH cte AS (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT 1)) RowNum,
                t.c.value(''.'',''nvarchar(max)'') as [Values]
        FROM @x.nodes(''/row/p'') as t(c)
    )
    
    SELECT '+STUFF(@col,1,1,'')+'
    FROM (
        SELECT  RowNum - CASE WHEN RowNum%@i = 0 THEN @i ELSE RowNum%@i END Seq ,
                CASE WHEN RowNum%@i = 0 THEN @i ELSE RowNum%@i END as [ColumnNum],
                [Values]
        FROM cte
    ) as t
    PIVOT (
        MAX([Values]) FOR [ColumnNum] IN ('+STUFF(@col,1,1,'')+')
    ) as pvt'
    
    EXEC sp_executesql @sql, @params, @x = @x, @i = @i
    
    对于
    A,B;D,E;X,Y

    1   2
    A   B
    D   E
    X   Y
    

    在这个解决方案中,我将尽可能多地使用字符串操作
    DECLARE @source VARCHAR(max) = 'A1,B1,C1,D1,E1,F1,G1;A2,B2,C2,D2,E2,F2,G2;A3,B3,C3,D3,E3,F3,G3;A4,B4,C4,D4,E4,F4,G4;A5,B5,C5,D5,E5,F5,G5;A6,B6,C6,D6,E6,F6,G6;A7,B7,C7,D7,E7,F7,G7;A8,B8,C8,D8,E8,F8,G8;A9,B9,C9,D9,E9,F9,G9;A10,B10,C10,D10,E10,F10,G10;A11,B11,C11,D11,E11,F11,G11;A12,B12,C12,D12,E12,F12,G12;A13,B13,C13,D13,E13,F13,G13;A14,B14,C14,D14,E14,F14,G14;A15,B15,C15,D15,E15,F15,G15;A16,B16,C16,D16,E16,F16,G16;A17,B17,C17,D17,E17,F17,G17;A18,B18,C18,D18,E18,F18,G18;A19,B19,C19,D19,E19,F19,G19;A20,B20,C20,D20,E20,F20,G20'
    
    -- First determine the columns names. Since the string can be potential very long we don’t want to parse the entire string to determine how many columns 
    -- we have, instead get sub string of main string up to first row delimiter.
    DECLARE @firstRow VARCHAR(max) = LEFT(@source, CHARINDEX(';', @source) - 1);
    DECLARE @columnNames NVARCHAR(MAX) = '';
    
    -- Use string splitter function on sub string to determine column names.
    SELECT @columnNames = STUFF(( 
                                    SELECT ',' + QUOTENAME(CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS VARCHAR(10)))
                                    FROM        [dbo].[SplitString](@firstRow, ',') Items
                                    FOR XML PATH('')), 1, 1, '');
    
    -- Next build dynamic query that will generate our matrix table.
    -- CTE first split string by row delimiters then it applies the string split function again on each row.  
    DECLARE @pivotQuery NVARCHAR(MAX) ='
    ;WITH CTE_SplitData AS
    (
    SELECT       R.Nr AS [Row]
                ,C.[Columns]
                ,ROW_NUMBER() OVER (PARTITION BY R.Nr ORDER BY R.Item) AS ColumnNr
    FROM        [dbo].[SplitString](@source, '';'') R
    OUTER APPLY (
                    SELECT  Item AS [Columns]
                    FROM    [dbo].[SplitString](R.Item, '','') 
                ) C
    )
    -- Pivoted reuslt
    SELECT * FROM
    (  
         SELECT * 
         FROM   CTE_SplitData
    )as T
    PIVOT 
    (
         max(T.[Columns])
         for T.[ColumnNr] in (' +  @columnNames + ')
    ) as P'
    
    
    EXEC sp_executesql  @pivotQuery,
              N'@source VARCHAR(MAX)',  
              @source = @source;        -- Pass the source string to be split as a parameter to the dynamic query.
    
    --declare @str varchar(max)='A,B;D,E;X,Y',
    declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z',
            @x xml,
            @col nvarchar(max),
            @sql nvarchar(max),
            @params nvarchar(max) = '@x xml, @i int',
            @i int
    
    SELECT  @x = CAST('<row>'+REPLACE(('<p>'+REPLACE(@str,',','</p><p>')+'</p>'),';','</p></row><row><p>')+'</row>' as xml),
            @str = REPLACE(@str,';',',;')+',;', 
            @i =  (LEN(@str)-LEN(REPLACE(@str,',','')))/(LEN(@str)-LEN(REPLACE(@str,';','')))
    
    ;WITH colsPiv AS (
        SELECT 1 as col
        UNION ALL
        SELECT col+1
        FROM colsPiv
        WHERE col < @i
    )
    
    SELECT @col = (
        SELECT ','+QUOTENAME(col)
        FROM colsPiv
        FOR XML PATH('')
    )
    
    SELECT @sql = N'
    ;WITH cte AS (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT 1)) RowNum,
                t.c.value(''.'',''nvarchar(max)'') as [Values]
        FROM @x.nodes(''/row/p'') as t(c)
    )
    
    SELECT '+STUFF(@col,1,1,'')+'
    FROM (
        SELECT  RowNum - CASE WHEN RowNum%@i = 0 THEN @i ELSE RowNum%@i END Seq ,
                CASE WHEN RowNum%@i = 0 THEN @i ELSE RowNum%@i END as [ColumnNum],
                [Values]
        FROM cte
    ) as t
    PIVOT (
        MAX([Values]) FOR [ColumnNum] IN ('+STUFF(@col,1,1,'')+')
    ) as pvt'
    
    EXEC sp_executesql @sql, @params, @x = @x, @i = @i
    
    1   2   3
    A   B   C
    D   E   F
    X   Y   Z
    
    1   2
    A   B
    D   E
    X   Y
    
    Create Proc dbo.Spliter
    (
        @str varchar(max), @RowSep char(1), @ColSep char(1)
    ) 
    as
        declare  @FirstRow varchar(max), @hdr varchar(max), @n int, @i int=0
    
    -- Generate the Column names
        set @FirstRow=iif(CHARINDEX(@RowSep, @str)=0, @str, Left(@str, CHARINDEX(@RowSep, @str)-1))
    
        set @n=LEN(@FirstRow) - len(REPLACE(@FirstRow, @ColSep,''))
        while @i<=@n begin
            Set @hdr= coalesce(@hdr+', ', '') + 'Col' +convert(varchar, @i)
            set @i+=1
        end
    
    --Convert the input string to a form suitable for Values keyword
    --i.e. similar to Values(('A'),('B'),('C')),(('D'),('E'),('F')), ...etc
        set @str =REPLACE(@str, @ColSep,'''),(''')
        set @str = 'Values((''' + REPLACE(@str, @RowSep, ''')),((''') + '''))'
    
        exec('SELECT * FROM (' + @str + ') as t('+@hdr+')')    
    
    -- exec dbo.Spliter 'A,B,C;D,E,F;X,Y,Z', ';', ','
    
    Create Proc dbo.Spliter2
    (
        @str varchar(max), @RowSep char(1), @ColSep char(1)
    ) 
    as
    
         declare  @FirstRow varchar(max), @hdr varchar(max), @ColCount int, @i int=0
     , @ColTemplate varchar(max)= 'Col.value(''(./c)[$]'', ''VARCHAR(max)'') AS Col$'
    
    -- Determin the number of columns
        set @FirstRow=iif(CHARINDEX(@RowSep, @str)=0, @str, Left(@str, CHARINDEX(@RowSep, @str)-1))
        set @ColCount = LEN(@FirstRow) - len(REPLACE(@FirstRow, @ColSep,''))
    
    -- Construct Column Headers by replacing the $ with the column number
    -- similar to: Col.value('(./c)[1]', 'VARCHAR(max)') AS Col1,     Col.value('(./c)[2]', 'VARCHAR(max)') AS Col2
        while @i<=@ColCount begin
            Set @hdr= coalesce(@hdr+', ', '') + Replace(@ColTemplate, '$', convert(varchar, @i+1))
            set @i+=1
        end
    
    -- Convert the input string to XML format
    -- similar to '<r><c>A</c><c>B</c><c>c</c></r> <r><c>D</c><c>E</c><c>f</c>    </r> 
        set @str='<c>'+replace(@str, ',', '</c>'+'<c>')+'</c>'
        set @str='<r>'+replace(@str  , ';', '</c></r><r><c>')+'</r>'
    
        set @str='SELECT ' +@HDR 
        + ' From(Values(Cast('''+@str+''' as xml))) as t1(x) 
            CROSS APPLY x.nodes(''/r'') as t2(Col)'
    
        exec( @str)
    
    -- exec dbo.Spliter2 'A,B,C;D,E,F;X,Y,Z', ';', ','
    
    Declare @Str varchar(max)= 'A,B,C;D,E,F;X,Y,Z';
    
    IF OBJECT_ID('tempdb..#RawData') IS NOT NULL
        DROP TABLE #RawData;
    ;WITH T_String AS
    (
        SELECT  RIGHT(@Str,LEN(@Str)-CHARINDEX(';',@Str,1)) AS RawString, LEFT(@Str,CHARINDEX(';',@Str,1)-1) AS RowString, 1 AS CounterValue,  len(@Str) - len(replace(@Str, ';', '')) AS RowSize
        --
        UNION ALL
        --
        SELECT  IIF(CHARINDEX(';',RawString,1)=0,NULL,RIGHT(RawString,LEN(RawString)-CHARINDEX(';',RawString,1))) AS RawString, IIF(CHARINDEX(';',RawString,1)=0,RawString,LEFT(RawString,CHARINDEX(';',RawString,1)-1)) AS RowString, CounterValue+1 AS CounterValue, RowSize AS RowSize
        FROM    T_String AS r
        WHERE   CounterValue <= RowSize
    )
    ,T_Columns AS
    (
        SELECT  RowString AS RowValue, RIGHT(a.RowString,LEN(a.RowString)-CHARINDEX(',',a.RowString,1)) AS RawString, 
                LEFT(a.RowString,CHARINDEX(',',a.RowString,1)-1) AS RowString, 1 AS CounterValue,  len(a.RowString) - len(replace(a.RowString, ',', '')) AS RowSize
        FROM    T_String AS a
        --WHERE a.CounterValue = 1
        --
        UNION ALL
        --
        SELECT  RowValue, IIF(CHARINDEX(',',RawString,1)=0,NULL,RIGHT(RawString,LEN(RawString)-CHARINDEX(',',RawString,1))) AS RawString, IIF(CHARINDEX(',',RawString,1)=0,RawString,LEFT(RawString,CHARINDEX(',',RawString,1)-1)) AS RowString, CounterValue+1 AS CounterValue, RowSize AS RowSize
        FROM    T_Columns AS r
        WHERE   CounterValue <= RowSize
    )
    ,T_Data_Prior2Pivot AS 
    (
        SELECT  c.RowValue, c.RowString, c.CounterValue
        FROM    T_Columns AS c
        INNER JOIN
                T_String AS r
            ON  r.RowString = c.RowValue
    )
    SELECT  *
    INTO    #RawData
    FROM    T_Data_Prior2Pivot;
    
    DECLARE @columnNames VARCHAR(MAX)
            ,@sqlQuery VARCHAR(MAX)
    SELECT @columnNames = COALESCE(@columnNames+', ['+CAST(CounterValue AS VARCHAR)+']','['+CAST(CounterValue AS VARCHAR)+']') FROM (SELECT DISTINCT CounterValue FROM #RawData) T
    PRINT @columnNames
    
    SET @sqlQuery = '
    SELECT  '+@columnNames+'
    FROM    ( SELECT * FROM #RawData 
            ) AS b
    PIVOT   (MAX(RowString) FOR CounterValue IN ('+@columnNames+')) AS p
    '
    
    EXEC (@sqlQuery);
    
    DECLARE @str varchar(max) = 'A,B,C;D,E,F;X,Y,Z';
    ;WITH cte
    AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rn, *
    FROM string_split(@str, ';')),
    cte2
    AS (SELECT rn, ROW_NUMBER() OVER (PARTITION BY rn ORDER BY (SELECT NULL)) rownum, val.value
    FROM cte c
    CROSS APPLY string_split(value, ',') val)
    SELECT
        [1], [2], [3]
    FROM cte2
    PIVOT (MAX(value) FOR rownum IN ([1], [2], [3])) p
    
    declare @str varchar(max)='A,B;D,E;X,Y';
    declare @sql nvarchar(max)
    declare @cols varchar(max) = ''
    
    ;with cte as (
    select row_number() over(order by (select null)) rn from string_split( substring(@str,1,charindex(';', @str)-1),',')
    ) select @cols=concat(@cols,',',quotename(rn)) from cte
    
    select @cols = stuff(@cols,1,1,'')
    set @sql = N'
    declare @str varchar(max)=''A,B;D,E;X,Y'';
    with cte as
    (
    select row_number() over( order by (select null)) as rn, * from string_split(@str,'';'')
    ), cte2 as (
    select rn, row_number() over(partition by rn order by (select null)) rownum,  val.value from cte c cross apply string_split(value,'','') val
    )
    select ' +@cols + '
    from cte2 
    pivot (max(value) for rownum in (' + @cols + ')) p '
    
    exec sp_executesql @sql
    
    IF OBJECT_ID('dbo.uspDumpMultilinesWithHeaderIntoTable', 'P') IS NOT NULL 
        DROP PROCEDURE dbo.uspDumpMultilinesWithHeaderIntoTable; 
    GO
    CREATE PROCEDURE dbo.uspDumpMultilinesWithHeaderIntoTable @TableName VARCHAR(32), @Multilines VARCHAR(MAX)
    AS
        SET NOCOUNT ON
        IF OBJECT_ID('tempdb..#RawData') IS NOT NULL DROP TABLE #RawData
        IF OBJECT_ID('tempdb..#RawDataColumnnames') IS NOT NULL DROP TABLE #RawDataColumnnames
        DECLARE @RowDelim VARCHAR(9) = '&#x0d;'
        DECLARE @ColDelim VARCHAR(9) = CHAR(9)
        DECLARE @MultilinesSafe VARCHAR(MAX)
        DECLARE @MultilinesXml XML--VARCHAR(MAX)
        DECLARE @ColumnNamesAsString VARCHAR(4000)
        DECLARE @SQL NVARCHAR(4000), @ParamDef NVARCHAR(4000)
    
        SET @MultilinesSafe = REPLACE(@Multilines, CHAR(10), '')    -- replace LF
        SET @MultilinesSafe = (SELECT REPLACE(@MultilinesSafe, CHAR(10), '') FOR XML PATH(''))   -- escape any XML confusion
        SET @MultilinesSafe = '<rows><row first="1"><cols><col first="1">' + REPLACE(REPLACE(@MultilinesSafe, @RowDelim, '</col></cols></row><row first="0"><cols><col first="0">'), @ColDelim, '</col><col>') + '</col></cols></row></rows>'
        SET @MultilinesXml = @MultilinesSafe
        --PRINT CAST(@MultilinesXml AS VARCHAR(MAX))
    
        -- extract Column names
        SELECT
            IDENTITY(INT, 1, 1) AS ID,
            t.n.query('.').value('.', 'VARCHAR(4000)') AS ColName
        INTO #RawDataColumnnames
        FROM @MultilinesXml.nodes('/rows/row[@first="1"]/cols/col') AS t(n) -- just first row
        ALTER TABLE #RawDataColumnnames ADD CONSTRAINT [PK_#RawDataColumnnames] PRIMARY KEY CLUSTERED(ID)
        -- now tidy any strange characters in column name
        UPDATE T SET ColName = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ColName, '.', '_'), ' ', '_'), '[', ''), ']', ''), '.', ''), '$', '') FROM #RawDataColumnnames T
    
        -- create output table
        SET @SQL = 'IF OBJECT_ID(''' + @TableName + ''') IS NOT NULL DROP TABLE ' + @TableName
        --PRINT 'TableDelete SQL=' + @SQL
        EXEC sp_executesql  @SQL
    
        SET @SQL = 'CREATE TABLE ' + @TableName + '('
        SELECT @SQL = @SQL + CASE T.ID WHEN 1 THEN '' ELSE ', ' END
            + CHAR(13) + '['+ T.ColName + '] VARCHAR(4000) NULL'
        FROM #RawDataColumnnames T
        ORDER BY ID
        SET @SQL = @SQL + ')'
        --PRINT 'TableCreate SQL=' + @SQL
        EXEC sp_executesql  @SQL
    
        -- insert data into output table
        SET @SQL = 'INSERT INTO ' + @TableName + ' SELECT '
        SELECT @SQL = @SQL + CONCAT(CHAR(13)
            , CASE T.ID WHEN 1 THEN ' ' ELSE ',' END
            , ' t.n.value(''col[', T.ID, ']'', ''VARCHAR(4000)'') AS TheCol', T.ID)
        FROM #RawDataColumnnames T
        ORDER BY ID
        SET @SQL = @SQL + CONCAT(CHAR(13), 'FROM @TheXml.nodes(''/rows/row[@first="0"]/cols'') as t(n)')
        --PRINT 'Insert SQL=' + @SQL
        SET @ParamDef = N'@TheXml XML'
        EXEC sp_ExecuteSql  @SQL, @ParamDef, @TheXml=@MultilinesXml
    
    GO
    
        EXEC dbo.uspDumpMultilinesWithHeaderIntoTable 'Deleteme', 'Left Centre  Right
    A   B   C
    D   E   F
    G   H   I'
    
    Left    Centre  Right
    A   B   C
    D   E   F
    G   H   I
    
        IF OBJECT_ID('dbo.uspDumpMultilinesWithHeaderIntoTable', 'P') IS NOT NULL DROP PROCEDURE dbo.uspDumpMultilinesWithHeaderIntoTable; 
    GO
    CREATE PROCEDURE dbo.uspDumpMultilinesWithHeaderIntoTable @TableName VARCHAR(127), @Multilines VARCHAR(MAX), @ColDelimDefault VARCHAR(9) = NULL, @Debug BIT = NULL
    AS
    SET NOCOUNT ON
    IF OBJECT_ID('tempdb..#RawData') IS NOT NULL DROP TABLE #RawData
    IF OBJECT_ID('tempdb..#RawDataColumnnames') IS NOT NULL DROP TABLE #RawDataColumnnames
    DECLARE @Msg VARCHAR(4000)
    DECLARE @PosCr INT, @PosNl INT, @TypeRowDelim VARCHAR(20)
    
    -- work out type of row delimiter(s)
    SET @PosCr = CHARINDEX(CHAR(13), @Multilines)
    SET @PosNl = CHARINDEX(CHAR(10), @Multilines)
    SET @TypeRowDelim = CASE
        WHEN @PosCr = @PosNl + 1 THEN 'NL_CR'
        WHEN @PosCr = @PosNl - 1 THEN 'CR_NL'
        WHEN @PosCr = 0 AND @PosNl > 0 THEN 'NL'
        WHEN @PosCr > 0 AND @PosNl = 0 THEN 'CR'
        ELSE CONCAT('? CR@', @PosCr, ', NL@', @PosNl, ' is unexpected') END
    
    -- CR(x0d) is a 'good' row delimiter - make the data fit
    DECLARE @RowDelim VARCHAR(9)
    
    DECLARE @MultilinesSafe VARCHAR(MAX)
    IF @TypeRowDelim = 'CR_NL' OR @TypeRowDelim = 'NL_CR' BEGIN
        SET @RowDelim = '&#x0d;'
        SET @MultilinesSafe = REPLACE(@Multilines, CHAR(10), '')    -- strip LF
        SET @MultilinesSafe = (SELECT @MultilinesSafe FOR XML PATH(''))  -- escape any XML confusion
    END 
    ELSE IF @TypeRowDelim = 'CR' BEGIN
        SET @RowDelim = '&#x0d;'
        SET @MultilinesSafe = @Multilines
        SET @MultilinesSafe = (SELECT @MultilinesSafe FOR XML PATH(''))  -- escape any XML confusion
    END
    ELSE IF @TypeRowDelim = 'NL' BEGIN
        SET @RowDelim = '&#x0d;'
        SET @MultilinesSafe = REPLACE(@Multilines, CHAR(10), CHAR(13))  -- change LF to CR
        SET @MultilinesSafe = (SELECT @MultilinesSafe FOR XML PATH(''))  -- escape any XML confusion
    END
    ELSE
        RAISERROR(@TypeRowDelim , 10, 10)
    
    DECLARE @ColDelim VARCHAR(9) = COALESCE(@ColDelimDefault, CHAR(9))
    DECLARE @MultilinesXml XML
    DECLARE @ColumnNamesAsString VARCHAR(4000)
    DECLARE @SQL NVARCHAR(4000), @ParamDef NVARCHAR(4000)
    
    IF @Debug = 1 BEGIN
        SET @Msg = CONCAT('TN=<', @TableName, '>, TypeRowDelim=<', @TypeRowDelim, '>, RowDelim(XML)=<', @RowDelim, '>, ColDelim=<', @ColDelim, '>, LEN(@Multilines)=', LEN(@Multilines))
        PRINT @Msg
    END
    
    SET @MultilinesSafe = '<rows><row first="1"><cols><col first="1">' + REPLACE(REPLACE(@MultilinesSafe, @RowDelim, '</col></cols></row><row first="0"><cols><col first="0">'), @ColDelim, '</col><col>') + '</col></cols></row></rows>'
    SET @MultilinesXml = @MultilinesSafe
    --IF @Debug = 1 PRINT CAST(@MultilinesXml AS VARCHAR(MAX))
    
    -- extract Column names
    SELECT
        IDENTITY(INT, 1, 1) AS ID,
        t.n.query('.').value('.', 'VARCHAR(4000)') AS ColName
    INTO #RawDataColumnnames
    FROM @MultilinesXml.nodes('/rows/row[@first="1"]/cols/col') AS t(n) -- just first row
    ALTER TABLE #RawDataColumnnames ADD CONSTRAINT [PK_#RawDataColumnnames] PRIMARY KEY CLUSTERED(ID)
    -- now tidy any strange characters in column name
    UPDATE T SET ColName = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ColName, '.', '_'), ' ', '_'), '[', ''), ']', ''), '.', ''), '$', '') FROM #RawDataColumnnames T
    -- now fix any empty column names
    UPDATE T SET ColName = CONCAT('_Col_', ID, '_') FROM #RawDataColumnnames T WHERE ColName = ''
    
    IF @Debug = 1 BEGIN
        SET @Msg = CONCAT('#Cols(FromHdr)=', (SELECT COUNT(*) FROM #RawDataColumnnames) )
        PRINT @Msg
    END
    
    -- create output table
    SET @SQL = 'IF OBJECT_ID(''' + @TableName + ''') IS NOT NULL DROP TABLE ' + @TableName
    --PRINT 'TableDelete SQL=' + @SQL
    EXEC sp_executesql  @SQL
    
    SET @SQL = 'CREATE TABLE ' + @TableName + '('
    
    SET @SQL = @SQL + '[_Row_PK_] INT IDENTITY(1,1) PRIMARY KEY,'   -- PK
    
    SELECT @SQL = @SQL + CASE T.ID WHEN 1 THEN '' ELSE ', ' END
        + CHAR(13) + '['+ T.ColName + '] VARCHAR(4000) NULL'
    FROM #RawDataColumnnames T
    ORDER BY ID
    
    SET @SQL = @SQL + ')'
    --PRINT 'TableCreate SQL=' + @SQL
    EXEC sp_executesql  @SQL
    
    -- insert data into output table
    SET @SQL = 'INSERT INTO ' + @TableName + ' SELECT '
    SELECT @SQL = @SQL + CONCAT(CHAR(13)
        , CASE T.ID WHEN 1 THEN ' ' ELSE ',' END
        , ' t.n.value(''col[', T.ID, ']'', ''VARCHAR(4000)'') AS TheCol', T.ID)
    FROM #RawDataColumnnames T
    ORDER BY ID
    SET @SQL = @SQL + CONCAT(CHAR(13), 'FROM @TheXml.nodes(''/rows/row[@first="0"]/cols'') as t(n)')
    --PRINT 'Insert SQL=' + @SQL
    SET @ParamDef = N'@TheXml XML'
    EXEC sp_ExecuteSql  @SQL, @ParamDef, @TheXml=@MultilinesXml
    
    GO
    
        EXEC dbo.uspDumpMultilinesWithHeaderIntoTable  'Deleteme', 'Left        Right
    A   B   C
    D   E   F
    G   H   I'
    
    _Row_PK_    Left    _Col_2_ Right
    1   A   B   C
    2   D   E   F
    3   G   H   I