如何将用逗号分隔的值转换为SQL Server中的新记录?

时间:2017-09-01 21:47:45

标签: sql sql-server tsql split

根据下面提到的场景,有些人可以解释一下如何用逗号分隔的值到SQL Server的新记录中。

我有一张这样的表

Column 1  |  Column 2
----------+--------------
abc       |  12345
bcd       |  13455,45678
sdf       |  78934,13345

我希望结果符合以下方式

Column1 |  Column2
--------+----------
abc     |   12345
bcd     |   13455
bcd     |   45678
sdf     |   78934
sdf     |   13345

3 个答案:

答案 0 :(得分:2)

从基于理货的字符串拆分功能开始,如Jeff Moden's DelimitedSplit8K

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== 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, 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
;
GO

现在你的问题变得非常简单......

IF OBJECT_ID('tempdb..#temp', 'U') IS NOT NULL
DROP TABLE #temp;

CREATE TABLE #temp (
    Column1 CHAR(3),
    Column2 VARCHAR(8000)
    );  
INSERT #temp (Column1,Column2) VALUES
    ('abc', '12345'),
    ('bcd', '13455,45678'),
    ('sdf', '78934,13345');

-- the actual query...
SELECT 
    t.Column1,
    dsk.Item
FROM 
    #temp t
    CROSS APPLY dbo.DelimitedSplit8K(t.Column2, ',') dsk;

结果...

Column1 Column2
------- --------
abc     12345
bcd     13455
bcd     45678
sdf     78934
sdf     13345

编辑:上面假设Column2可以在CSV字符串中包含任意数量的元素。如果元素的最大数量为2,则可以跳过拆分器功能并使用以下内容...

SELECT 
    t.Column1,
    v.Column2
FROM 
    #temp t
    CROSS APPLY ( VALUES (NULLIF(CHARINDEX(',', t.Column2, 1), 0)) ) s (Split)
    CROSS APPLY ( VALUES (1, LEFT(t.Column2, s.Split - 1)), (2, substring(t.Column2, ISNULL(s.Split, 0) + 1, 8000)) ) v (rn, Column2)
WHERE
    v.Column2 IS NOT NULL
ORDER BY
    v.rn;

答案 1 :(得分:2)

Jason的回答将是我的第一选择(1 +)

但是,如果您无法使用(或想要)TVF,这是一种在线方法。

示例

Select A.[Column 1] 
      ,[Column 2] = B.RetVal
 From  YourTable A
 Cross Apply (
                Select RetSeq = Row_Number() over (Order By (Select null))
                      ,RetVal = LTrim(RTrim(B2.i.value('(./text())[1]', 'varchar(max)')))
                From  (Select x = Cast('<x>' + replace([Column 2],',','</x><x>')+'</x>' as xml).query('.')) B1
                Cross Apply x.nodes('x') AS B2(i)
             ) B

<强>返回

Column 1    Column 2
abc         12345
bcd         13455
bcd         45678
sdf         78934
sdf         13345

答案 2 :(得分:0)

您可以使用如下所示的基于Tally表的分割器:

select 
    column1,
    split_values
from 
(
    select 
        t.column1,
        SUBSTRING( t.column2, t1.N, ISNULL(NULLIF(CHARINDEX(',',t.column2,t1.N),0)-t1.N,8000)) as split_values
    from @t t 
        join
        (
            select 
                t.column2,
                1 as N 
            from @t t  
                UNION ALL
            select 
                t.column2,
                t1.N + 1 as N
            from @t t 
                join
                (
                 select 
                    top 8000
                        row_number() over(order by (select NULL)) as N 
                 from 
                    sys.objects s1 
                        cross join 
                   sys.objects s2 
                ) t1 
            on SUBSTRING(t.column2,t1.N,1) = ','
         ) t1
         on t1.column2=t.column2
)a
order by column1 

See live demo