【问题标题】:Split a string with no delimiters into columns将没有分隔符的字符串拆分为列
【发布时间】:2015-06-11 13:50:27
【问题描述】:

我需要在 SQL Server 2012 中将一列中的字符串拆分为一个字符,每个字符放入它自己的列中。

示例:如果我有一个包含'ABCDE' 的列,我需要将其拆分为'A''B''C''D''E',并将它们中的每一个拆分为各自的列。

要拆分的列的长度可能会有所不同,所以我需要它尽可能动态。

我的问题与其他帖子 (Can Mysql Split a column?) 不同,因为我的问题没有任何分隔符。 谢谢

【问题讨论】:

  • 选择子串(col,1,1) 作为c1,子串(col,2,1) 作为c2, ... ?

标签: sql sql-server database


【解决方案1】:

你可以这样做:

DECLARE @t TABLE(id int, n VARCHAR(50))
INSERT INTO @t VALUES
(1, 'ABCDEF'),
(2, 'EFGHIJKLMNOPQ')


;WITH cte AS
(SELECT id, n, SUBSTRING(n, 1, 1) c, 1 AS ind FROM @t
 UNION ALL 
 SELECT id, n, SUBSTRING(n, ind + 1, 1), ind + 1 FROM cte WHERE LEN(n) > ind
)

SELECT *
FROM cte 
PIVOT (MAX(c) FOR ind IN([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[12],[13],[14],[15])) p

输出:

id  n               1   2   3   4   5   6   7    8    9    10   12   13   14    15
1   ABCDEF          A   B   C   D   E   F   NULL NULL NULL NULL NULL NULL NULL  NULL
2   EFGHIJKLMNOPQ   E   F   G   H   I   J   K    L    M    N    P    Q    NULL  NULL

这里是动态版本:

DECLARE @l INT, @c VARCHAR(MAX) = ''
SELECT @l = MAX(LEN(n)) FROM PivotTable

WHILE @l > 0
BEGIN
 SET @c = ',[' + CAST(@l AS VARCHAR(MAX)) + ']' + @c
 SET @l = @l - 1
END

SET @c = STUFF(@c, 1, 1,'')

DECLARE @s NVARCHAR(MAX) = '
;WITH cte AS
(SELECT id, n, SUBSTRING(n, 1, 1) c, 1 AS ind FROM PivotTable
 UNION ALL 
 SELECT id, n, SUBSTRING(n, ind + 1, 1), ind + 1 FROM cte WHERE LEN(n) > ind
)

SELECT *
FROM cte 
PIVOT (MAX(c) FOR ind IN(' + @c + ')) p'

EXEC (@s)

【讨论】:

    【解决方案2】:

    我将问题解释为将字符放入一列(“将一列中的字符串拆分为一个字符,每个字符放入它自己的列”)。但是,我意识到这可能是模棱两可的。

    一种方法是使用递归 CTE:

    with chars as (
          select left(val, 1) as c, substring(val, 2, len(val)) as rest
          from (select 'ABCDE' as val union all select '123') t
          union all
          select left(rest, 1), substring(rest, 2, len(rest))
          from chars
          where rest <> ''
         )
    select c
    from chars;
    

    只需在子查询中插入您的表和列。请注意,您可能还想包含其他列。

    Here 是一个 SQL Fiddle。

    如果你想要多列并且数量不固定,那么你需要 动态 SQL。

    【讨论】:

    • 对于这样的事情来说,这是一个很好的拆分,但它会留下行而不是列。我认为它需要与动态枢轴配对才能获得所需的结果。当然,如果这是一个真实的、有用的预期结果。 [你刚刚在编辑中提到了一点。]
    【解决方案3】:

    如果你想为你只需要的每个字符创建一个新列:

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

    如果您知道列数,这很好。如果您有未知数量的列,那么您只需要使用 n 列生成相同的 SQL。为此,您需要一个数字表,而且由于很多人没有数字表,因此我将快速演示如何动态生成数字表。

    下面将生成一个连续的数字列表,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;
    

    它只是使用表值构造函数生成 10 行(N1),然后交叉连接这 10 行得到 100 行(N2),然后交叉连接这 100 行得到 10,000 行(N3 ) 等等等等。它最终使用ROW_NUMBER() 来获取序列号。

    这可能需要减少用于此用途,我希望您不要拆分 100,000,000 个字符长的字符串,但原则适用。您可以使用TOP 和字符串的最大长度来限制它。对于每个数字,您只需构建必要的重复 SQL,即:

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

    所以你有类似的东西:

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

    这给出了类似的东西:

    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)
    

    最后一步是通过连接SQL 列中的所有文本来构建您的最终语句;最好的方法是使用 SQL Server 的 XML 扩展。

    所以你的最终查询可能会像这样结束:

    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;
    

    这给出了:

    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
    

    最后,如果你真的想把它分成几行,我还是会用同样的方法,用你的临时数字表,把它加入你原来的表:

    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;
    

    这给出了类似的东西:

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

    【讨论】:

      【解决方案4】:

      一种方式

      declare @str varchar(max) = 'ABCDE'
      declare @sql nvarchar(max) = ''
      declare @i int = 1
      while (@i <= len(@str)) begin
          set @sql += case when @i > 1 then ',' else '' end + '''' + substring(@str, @i, 1) + ''''
          set @i += 1 
      end
      
      exec('select ' + @sql)
      

      (如果' 可以显示为字符,则需要替换''

      【讨论】:

        【解决方案5】:

        这是动态文本长度的解决方案。

        -- Generate demo data
        CREATE TABLE #temp(col nvarchar(100))
        
        INSERT INTO #temp(col)
        VALUES(N'A'),(N'ABC'),(N'DEFGHI'),(N'AB'),(N'KLOMA')
        
        -- Split all in multiple rows
        CREATE TABLE #output (col nvarchar(100),part nchar(1), pos int)
        
        ;WITH cte AS(
            SELECT col, LEFT(col, 1) as part, 1 as pos
            FROM #temp
            UNION ALL
            SELECT col, SUBSTRING(col, pos+1,1) as part, pos+1 as part
            FROM cte
            WHERE LEN(col) > pos
        )
        INSERT INTO #output(col, part, pos)
            SELECT col, part, pos
            FROM cte
        
        DECLARE @sql nvarchar(max), @columnlist nvarchar(max)
        
        -- Generate Columlist for dynamic pivot
        SELECT @columnlist = COALESCE(@columnlist + N',[' + CONVERT(nvarchar(max),pos) + ']', N'[' + CONVERT(nvarchar(max),pos) + ']')
        FROM #output o
        WHERE o.col = (SELECT TOP (1) col FROM #output ORDER BY LEN(col) DESC)
        
        -- Pivoting for readability
        SET @sql = N'
        SELECT pvt.* 
        FROM #output o
        PIVOT (
            MAX(o.part)
            FOR pos IN('+@columnlist+')
        ) as pvt'
        EXEC (@sql)
        
        -- Cleanup
        DROP TABLE #temp
        DROP TABLE #output
        

        关键部分是 cte 和之后的旋转。如果您有任何问题,请给我一个简短的反馈。

        【讨论】:

          猜你喜欢
          • 2012-11-07
          • 1970-01-01
          • 1970-01-01
          • 2020-06-18
          • 1970-01-01
          • 2014-06-29
          • 1970-01-01
          • 2023-03-14
          相关资源
          最近更新 更多