복붙노트

[SQL] 행 구분 기호 및 열 구분 SQL 서버에서 주어진 테이블에 분할 문자열

SQL

행 구분 기호 및 열 구분 SQL 서버에서 주어진 테이블에 분할 문자열

어떻게 SQL Server의 테이블에 매트릭스를 포함하는 문자열을 분할하려면? 문자열 컬럼 및 행 구분 기호가 있습니다.

내가 문자열이 있다고 가정 :

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

(세 개의 열에서) 예상 결과 :

+---+---+---+
| A | B | C |
+---+---+---+
| D | E | F |
+---+---+---+
| X | Y | Z |
+---+---+---+

나는 열과 행의 수를 정의하지 않은 일반 솔루션을 찾고 있어요. 문자열 그래서 :

declare @str varchar(max)='A,B;D,E';

두 개의 열이있는 테이블로 분할됩니다 :

+---+---+
| A | B |
+---+---+
| D | E |
+---+---+

내 노력. 내 첫번째 생각에 문자열을집니다 동적 SQL을 사용하는 것이 었습니다 : dbo.temp 값 (...) 처음 컬럼의 오른쪽 수있는 테이블을 만들 필요하기 때문에 매우 빠른 미성년자의 단점을 가지고 있지만이 접근 방식에 삽입합니다. 단지 짧은 질문을 유지하기 위해 아래 나는 내 자신의 질문에 대한 답변에서이 방법을 제시 하였다.

또 다른 아이디어는에서 다음 서버 및 대량 삽입에 CSV 파일에 문자열을 작성하는 것입니다. 나는 그것을 수행하는 방법을 알고하지 않습니다하지만 무슨 일이 제 1 및 제 2 아이디어의 성능 일 것이다.

내가 SQL 서버에 엑셀에서 데이터 가져 오기를 원하기 때문에 나는이 질문을하는 이유입니다. 나는 다른 ADO 방식으로 실험 한 바와 같이, 매트릭스 문자열을 보내는이 방법은 문자열의 증가, 특히 길이, 압도적 인 승리입니다. 여기 질문의 젊은 쌍둥이 형제를 질문 : 어떻게 엑셀 범위에서 같은 문자열을 준비하는 제안을 찾을 수 VBA 문자열로 엑셀 범위를 설정합니다.

바운티 나는 수상 매트로 결정했다. 내가보기 엔 숀 랭의 답변을 정도. 당신에게 숀 감사드립니다. 나는 그것의 단순성과 곤란을 위해 매트의 답변을 좋아했다. (마지막으로, 몇 개월 후, 내가 수락 매트의 대답 갱신) 내가 어떤 대답을 수락하지하고 당분간 있도록 매트의 숀의에서 떨어져 다른 접근 방식은 병렬로 사용 될 수 있습니다. 나는 내가 시작했다 대답의 멋진 진화가 들어 값으로 자신의 아이디어를 아메드 사이드 감사하고 싶습니다. 물론, 그것은 매트 나 숀의에 일치하지 않습니다. 나는 모든 대답을 upvoted. 나는이 방법을 사용하여 당신에게서 어떤 의견을 보내 주셔서 감사합니다. 퀘스트 주셔서 감사합니다.

해결법

  1. ==============================

    1.쉬운 방법 중 하나는 구분 기호를 교체를 기반으로 XML로 문자열을 변환하는 것입니다.

    쉬운 방법 중 하나는 구분 기호를 교체를 기반으로 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)
    

    또는 동적으로

    DECLARE @str varchar(max)='A,B,C,D,E;F,G,H,I,J;K,L,M,N,O';
    DECLARE @NumOfColumns INT
    SET @NumOfColumns = (LEN(@str) - LEN(REPLACE(@str,',',''))) / (LEN(@str) - LEN(REPLACE(@str,';','')) + 1) + 1
    
    DECLARE @xmlstr XML
    SET @xmlstr = CAST(('<rows><row><col>' + REPLACE(REPLACE(@str,';','</col></row><row><col>'),',','</col><col>') + '</col></row></rows>') AS XML)
    
    DECLARE @ParameterDef NVARCHAR(MAX) = N'@XMLInputString xml'
    DECLARE @SQL NVARCHAR(MAX) = 'SELECT '
    
    DECLARE @i INT = 1
    
    WHILE @i <= @NumOfColumns
    BEGIN
        SET @SQL = @SQL + IIF(@i > 1,',','') + 't.n.value(''col[' + CAST(@i AS VARCHAR(10)) + ']'',''NVARCHAR(MAX)'') as Col' + CAST(@i AS VARCHAR(10))
    
        SET @i = @i + 1
    END
    
    SET @SQL = @SQL + ' FROM
        @XMLInputString.nodes (''/rows/row'') AS t(n)'
    
    EXECUTE sp_executesql @SQL,@ParameterDef,@XMLInputString = @xmlstr
    
  2. ==============================

    2.내가 어떤 루프없이이 작업을 수행 할 수 있는지를 결정하므로 확인이 퍼즐은 나에게 흥미. 작업이 전제 조건이 몇 가지 있습니다. 첫 번째는 우리는 당신이 집계 테이블의 일종 있다고 가정 것이다. 경우 여기에 해당이없는 광산에 대한 코드입니다. 나는 내가 사용하는 모든 시스템에 보관하십시오.

    내가 어떤 루프없이이 작업을 수행 할 수 있는지를 결정하므로 확인이 퍼즐은 나에게 흥미. 작업이 전제 조건이 몇 가지 있습니다. 첫 번째는 우리는 당신이 집계 테이블의 일종 있다고 가정 것이다. 경우 여기에 해당이없는 광산에 대한 코드입니다. 나는 내가 사용하는 모든 시스템에 보관하십시오.

    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
    

    이 퍼즐의 두번째 조각 세트 기반 문자열 스플리터를 필요로한다. 이것에 대한 내 취향은 동네 짱의 빠른 제프 MODEN 스플리터입니다. 한 가지주의해야 할 점은 단지 최대 8,000 VARCHAR 값으로 작동한다는 것입니다. 이것은 내가 작업을 가장 구분되는 스트링에 대한 충분한입니다. 현재 제프 MODEN의 스플리터 (DelimitedSplit8K를) 찾을 수 있습니다.

    http://www.sqlservercentral.com/articles/Tally+Table/72993/

    마지막으로는 없습니다 적어도 내가 여기에 사용하고있는 기술은 동적 크로스 탭 것입니다. 이 제프 MODEN에서 배운 것을 다른 I입니다. 그는 여기에 주제에 대한 좋은 기사가 있습니다.

    http://www.sqlservercentral.com/articles/Crosstab/65048/

    함께 당신이 정말 빠른 것 잘 확장 할 수있는이 같은 것을 가지고 올 수이 모든 퍼팅.

    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
    

    --편집하다--

    여기 링크가 잘못 될 경우에 DelimitedSplit8K 기능입니다.

    ALTER FUNCTION [dbo].[DelimitedSplit8K]
    --===== Define I/O parameters
            (@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 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, 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 "zero base" and limits the number of rows right up front
                         -- for both a performance gain and prevention of accidental "overruns"
                     SELECT 0 UNION ALL
                     SELECT TOP (DATALENGTH(ISNULL(@pString,1))) 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 t.N+1
                       FROM cteTally t
                      WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
                    )
    --===== 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 s.N1),
            Item       = SUBSTRING(@pString,s.N1,ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000))
       FROM cteStart s
    ;
    
  3. ==============================

    3.다음 코드는 SQL 서버에서 작동합니다. 그것은 약간의 조작으로 공통 테이블 표현식 및 동적 SQL을 사용합니다. 그냥 @str 변수에 문자열 값을 할당하고 한 번에 전체 코드를 실행합니다. 이 CTE를 사용하기 때문에, 각각의 단계에서 데이터 분석을 용이하다.

    다음 코드는 SQL 서버에서 작동합니다. 그것은 약간의 조작으로 공통 테이블 표현식 및 동적 SQL을 사용합니다. 그냥 @str 변수에 문자열 값을 할당하고 한 번에 전체 코드를 실행합니다. 이 CTE를 사용하기 때문에, 각각의 단계에서 데이터 분석을 용이하다.

    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);
    

    다음은 http://statisticsparser.com/에서 위의 질의에 대한 통계의 스크린 샷이다.

  4. ==============================

    4.

    **--Using dynamic queries..**
    
    
    declare @str varchar(max)='A,B,C;D,E,F;X,Y,Z';
    
    declare @cc int
    
    select @cc = len (substring (@str, 0, charindex(';', @str))) - len(replace(substring (@str, 0, charindex(';', @str)), ',', ''))
    
    declare @ctq varchar(max) = 'create table t('
    
    
    declare @i int = 0 
    
    while @i <= @cc
    begin
    
            select @ctq = @ctq + 'column' + char(65 + @i) + ' varchar(max), '
            select  @i = @i + 1
    end
    
    select  @ctq = @ctq + ')'
    
    select  @str = '''' + replace(@str, ',', ''',''') + ''''
    
    select @str = 'insert t select ' + @str
    
    select  @str = replace (@str, ';', ''' union all select ''')
    
    exec(@ctq)
    
    exec(@str)
    
  5. ==============================

    5.난 그냥 질문을 할 때 내가 시간에 사용하는 것을 보여주고, 질문을 확장하는 내 질문에 대한 답변을 게시 할 수 있습니다.

    난 그냥 질문을 할 때 내가 시간에 사용하는 것을 보여주고, 질문을 확장하는 내 질문에 대한 답변을 게시 할 수 있습니다.

    아이디어로 원래 문자열을 변경하는 것입니다 :

    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
    

    코드와 시저를 실행 :

    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을 만들 필요하다. 그것은 약간의 단점이다. dbo.temp으로 선택 *는 구조를 가지고있는 경우가있을 수 있도록 동적 따라서 용액이 아니다. 그럼에도 불구하고 나는 그것이 작동하기 때문에이 솔루션을 공유 할 그것은 빠르고, 간단하고, 어쩌면 다른 답변에 대한 영감이 될 것입니다.

  6. ==============================

    6.이 문제는 임시 테이블, 뷰, 루프 또는 XML에 대한 필요없이 해결 될 수있다. 먼저 아래의 예에 따라 집계 테이블을 기반으로 문자열 스플리터 기능을 만들 수 있습니다 :

    이 문제는 임시 테이블, 뷰, 루프 또는 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;
    

    먼저 스플릿 행 구분에 기초하여, 문자열을 분할 기능을 사용한다. 그런 다음 외부 문을 적용하여 각 행을 다시 분할 기능을 적용 할 수 있습니다. 마지막으로 결과를 돌리십시오. 열의 수를 알 수 있기 때문에 쿼리는 아래의 예에 따라 동적 SQL로 실행되어야 할 것이다 :

    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.
    
  7. ==============================

    7.회전 및 동적 SQL 일부 XML.

    회전 및 동적 SQL 일부 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
    

    D, E, F; A, B, C에 대한 출력 X, Y, Z :

    1   2   3
    A   B   C
    D   E   F
    X   Y   Z
    

    A, B를 들어, D, E, X, Y :

    1   2
    A   B
    D   E
    X   Y
    
  8. ==============================

    8.이 솔루션에 나는 가능한 한 문자열 조작을 사용합니다. 절차는 키워드 VALUES에 적합한 형태로 입력 된 문자열로 변환하여 동적 SQL 문을 구성하는 열 수를 카운트함으로써 열 타이틀을 구축하고, 필요한 헤더를 생성한다. 그런 다음, 간단하게 구성된 SQL 문을 실행합니다.

    이 솔루션에 나는 가능한 한 문자열 조작을 사용합니다. 절차는 키워드 VALUES에 적합한 형태로 입력 된 문자열로 변환하여 동적 SQL 문을 구성하는 열 수를 카운트함으로써 열 타이틀을 구축하고, 필요한 헤더를 생성한다. 그런 다음, 간단하게 구성된 SQL 문을 실행합니다.

    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', ';', ','
    

    방법-2 :

    값은 같은 PrzemyslawRemin로 표시 제한하는 1,000 행의 문제를 극복하기 위해, 여기에 하나의 행의 XML 필드로 변환 입력 문자열에 작은 수정하고 다음 CROSS은 개별 요소에 적용합니다.

    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', ';', ','
    
  9. ==============================

    9.여기에 또 다른 접근 방법이다.

    여기에 또 다른 접근 방법이다.

    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
    

    이제이 아래에 나열되어있는 파서를 필요로하지 않습니다 :

    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
    1       A,B,C
    2       D,E,F
    3       X,Y,Z
    

    이 후 다음을 반환하고 임시 테이블에 저장되는 크로스가 적용을 통해 다시 구문 분석

    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
    
    Declare @String varchar(max)='A,B,C;D,E,F;X,Y,Z';
    
    Declare @SQL varchar(max) = '',@Col int = Len(Left(@String,CharIndex(';',@String)-1))-Len(replace(Left(@String,CharIndex(';',@String)-1),',',''))+1
    Select  @SQL = @SQL+SQL From (Select Top (@Col) SQL=Concat(',xRow.xNode.value(''col[',N,']'',''varchar(max)'') as Col',N) From (Select N From (Values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) N(N) ) N ) A
    Select  @SQL = Replace('Declare @XML XML = Cast((''<row><col>'' + Replace(Replace(''[getString]'','';'',''</col></row><row><col>''),'','',''</col><col>'') + ''</col></row>'') as XML);Select '+Stuff(@SQL,1,1,'')+' From @XML.nodes(''/row'') AS xRow(xNode) ','[getString]',@String)
    Exec (@SQL)
    

    보고

    Col1    Col2    Col3
    A       B       C
    D       E       F
    X       Y       Z
    
  10. ==============================

    10.여기서 분할 지정 기능을 이용하여 동적 PIVOT 통해이를 수행하는 방법은 :

    여기서 분할 지정 기능을 이용하여 동적 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
    
  11. ==============================

    11.내 솔루션 string_split 물건을 사용하고 있습니다 .. 첫째가 어떻게 작동하는지에 대한 예입니다

    내 솔루션 string_split 물건을 사용하고 있습니다 .. 첫째가 어떻게 작동하는지에 대한 예입니다

    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
    

    동적를 사용하여 우리는 열 목록을 확인할 수 있으며 모든 입력을 위해 작동 할 SQL

    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
    

    SQL Server를 사용하는 경우 <2016 우리는 우리 자신의 분할 기능을 쓸 수 있습니다

  12. ==============================

    12.다음은 영업 이익이 질문 꽤 무엇 아니지만, 그것은 올바른 열 이름을 가진 SQL 테이블에 열 머리글로 CSV (실제로 탭-SV)과 같은 스프레드 시트를 수출하고, 변환, 나를 위해 편리한이었다.

    다음은 영업 이익이 질문 꽤 무엇 아니지만, 그것은 올바른 열 이름을 가진 SQL 테이블에 열 머리글로 CSV (실제로 탭-SV)과 같은 스프레드 시트를 수출하고, 변환, 나를 위해 편리한이었다.

    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'
    

    (비아 'deleteme FROM SELECT *')에

    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
    
  13. from https://stackoverflow.com/questions/39752188/split-string-into-table-given-row-delimiter-and-column-delimiter-in-sql-server by cc-by-sa and MIT license