Split a string with no delimiters into columns

后端 未结 5 1341
清歌不尽
清歌不尽 2021-01-26 12:40

I need to split a string in a column into one character each into it\'s own column in SQL Server 2012.

Example: if I have a column with \'ABCDE\', I need to

5条回答
  •  天命终不由人
    2021-01-26 13:06

    If you want a new column for every character you simply need:

    SELECT  [1] = SUBSTRING(Col, 1, 1),
            [2] = SUBSTRING(Col, 2, 1),
            [3] = SUBSTRING(Col, 3, 1),
            [4] = SUBSTRING(Col, 4, 1),
            [5] = SUBSTRING(Col, 5, 1),
            [6] = SUBSTRING(Col, 6, 1),
            [7] = SUBSTRING(Col, 7, 1),
            [8] = SUBSTRING(Col, 8, 1),
            [9] = SUBSTRING(Col, 9, 1)
    FROM    (VALUES ('ABCDE'), ('FGHIJKLMN')) t (Col);
    

    Which is fine, if you have a know number of columns. If you have an unknown number of columns, then you just need to generate the same SQL with n columns. To do this you will need a numbers table, and since many people do not have one, I will do a quick demo on how to dynamically generate one.

    The below will generate a sequential list of numbers, 1 - 100,000,000.

    WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
    N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
    N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
    Numbers (Number) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N3 AS N1 CROSS JOIN N3 AS N2)
    SELECT  Number
    FROM    Numbers;
    

    It simply uses a table valued constructor to generate 10 rows (N1), then cross joins these 10 rows to get 100 rows (N2), then cross joins these 100 rows to get 10,000 rows (N3) and so on and so on. It finally uses ROW_NUMBER() to get the sequential numbers.

    This probably needs to be cut down for this use, I hope you are not splitting a string that is 100,000,000 characters long, but the principle applies. You can just use TOP and the maximum length of your string to limit it. For each number you can just build up the necessary repetetive SQL required, which is:

    ,[n] = SUBSTRING(Col, n, 1)
    

    So you have something like:

    SELECT  Number,
            [SQL] = ',[' + CAST(Number AS VARCHAR(10)) + '] = SUBSTRING(Col, ' + CAST(Number AS VARCHAR(10)) + ', 1)'
    FROM    Numbers;
    

    Which gives something like:

    Number      SQL
    -----------------------------------
    1       ,[1] = SUBSTRING(Col, 1, 1)
    2       ,[2] = SUBSTRING(Col, 2, 1)
    3       ,[3] = SUBSTRING(Col, 3, 1)
    4       ,[4] = SUBSTRING(Col, 4, 1)
    

    The final step is to build up your final statement by concatenating all the text in the column SQL; the best way to do this is using SQL Server's XML Extensions.

    So your final query might end up like:

    DECLARE @SQL NVARCHAR(MAX) = '';
    
    IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
    CREATE TABLE #T (Col VARCHAR(100));
    INSERT #T (Col) VALUES ('ABCDE'), ('FGHIJKLMN');
    
    WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
    N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
    N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
    Numbers (Number) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N3 AS N1 CROSS JOIN N3 AS N2)
    
    SELECT  @SQL = 'SELECT Col' + 
                    (   SELECT  TOP (SELECT MAX(LEN(Col)) FROM #T) 
                                ',[' + CAST(Number AS VARCHAR(10)) + '] = SUBSTRING(Col, ' + CAST(Number AS VARCHAR(10)) + ', 1)'
                        FROM    Numbers
                        FOR XML PATH(''), TYPE
                    ).value('.', 'VARCHAR(MAX)') + '
                    FROM #T;';
    
    EXECUTE sp_executesql @SQL;
    

    Which gives:

    Col         1   2   3   4   5   6   7   8   9
    -------------------------------------------------
    ABCDE       A   B   C   D   E               
    FGHIJKLMN   F   G   H   I   J   K   L   M   N
    

    Finally, if you actually wanted to split it into rows, I would still use the same approach, with your adhoc numbers table, just join it to your original table:

    IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
    CREATE TABLE #T (Col VARCHAR(100));
    INSERT #T (Col) VALUES ('ABCDE'), ('FGHIJKLMN');
    
    WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
    N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
    N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
    Numbers (Number) AS (SELECT TOP (SELECT MAX(LEN(Col)) FROM #T) ROW_NUMBER() OVER(ORDER BY N1.N) FROM N3 AS N1 CROSS JOIN N3 AS N2)
    
    SELECT  t.Col,
            Position = n.Number,
            Character = SUBSTRING(t.Col, n.Number, 1)
    FROM    #T AS t
            INNER JOIN Numbers AS n
                ON n.Number <= LEN(t.Col)
    ORDER BY t.Col, n.Number;
    

    Which gives something like:

    Col     Position    Character
    -------------------------------
    ABCDE   1           A
    ABCDE   2           B
    ABCDE   3           C
    ABCDE   4           D
    ABCDE   5           E
    

提交回复
热议问题