将不同的varchar数据从一列拆分为多行

时间:2014-12-04 05:26:05

标签: sql sql-server

如何将CallTraversalLog列的值分隔为每行数据。结果集不得包含任何重复项

SeqNum  CallId  DNI     CallTraversalLog
94329   688     29636   CUSTSEG
94329   688     29636   CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;TPINCHANGE
94332   696     29636   CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;TPINCHANGE
94333   699     29636   CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;CRCENROLL
94333   699     29636   CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;CRCENROLL
94333   702     29636   CUSTSEG;CARDMNU;CRC;CRCBISOA;CRCBI
94334   703     29636   CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;CRCENROLL
94334   703     29636   CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;CRCENROLL
94334   706     29636   CUSTSEG;CARDMNU;CRC;CRCBISOA;CRCBI
94336   710     29636   CUSTSEG;PHPM;PBLOGIN;PBMENU
94340   714     29636   CUSTSEG;PHPM;PBLOGIN;PBMENU;PBBI;PBLST3

我现在迷路了,因为我不知道如何做到这一点。

结果应该是:

Code
CUSTSEG
PHPM
PBLOGIN
PBBI
PBLST3
CARDMNU
CRC
CRCACTIVATION
CRCENROLL
CRCBISOA
CRCBI
TPINCHANGE

我试过这个解决方案,但我得到了

  

Msg 9411,Level 16,State 1,Line 1
  XML解析:第1行,第37个字符,预期分号

我试过的命令是:

SELECT DISTINCT 
    Split.a.value('.', 'VARCHAR(100)') data
FROM   
    (SELECT 
        CAST('<M>' + Replace(CallTraversalLog, ';', '</M><M>') + '</M>' AS XML) AS Data
     FROM   
        tblReportData) AS A
CROSS APPLY 
    Data.nodes ('/M') AS Split(a) 
编辑:我想我知道为什么会收到此错误。某些记录仅包含特殊字符&。如何替换特殊字符以使查询起作用。

5 个答案:

答案 0 :(得分:2)

特殊字符很少,无法直接由XML解析。

您需要将它们替换为以节点值转义

& - &amp;
< - &lt;
> - &gt;
" - &quot;
' - &#39;

SELECT DISTINCT Split.a.value('.', 'VARCHAR(100)') data
FROM   (SELECT Cast('<M>'
                    + Replace(Replace(Replace(CallTraversalLog, ';', ','), '&', '&amp;'), ',', '</M><M>')
                    + '</M>' AS XML) AS Data
        FROM   tblReportData) AS A
       CROSS APPLY Data.nodes ('/M') AS Split(a) 

答案 1 :(得分:0)

您可以通过运行以下脚本来分隔值:

DECLARE @val1 varchar(200) = 'CUSTSEG;PHPM;CARDMNU;CRC;CRCACTIVATION;CRCENROLL'
DECLARE @val2 varchar(20) = null

WHILE LEN(@val1) > 0
BEGIN
    IF PATINDEX('%;%',@val1) > 0
    BEGIN
        SET @val2 = SUBSTRING(@val1, 0, PATINDEX('%;%',@val1))
        SELECT @val2

        SET @val1 = SUBSTRING(@val1, LEN(@val2 + ';') + 1, LEN(@val1))
    END
    ELSE
    BEGIN
        SET @val2 = @val1
        SET @val1 = NULL
        SELECT @val2
    END
END

希望这有帮助。

答案 2 :(得分:0)

使用此分裂功能(也处理一条记录)

 alter  FUNCTION [dbo].[fnSplitString] 
( 
    @string VARCHAR(MAX), 
    @delimiter CHAR(1) 
) 
RETURNS @output TABLE(splitdata NVARCHAR(MAX) 
) 
BEGIN 
IF  CHARINDEX(@delimiter, @string) IS NULL   --deal with one record
BEGIN
SET @string=@string+@delimiter

    DECLARE @start INT, @end INT 
    SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
    WHILE @start < LEN(@string) + 1 BEGIN 
        IF @end = 0  
            SET @end = LEN(@string) + 1

        INSERT INTO @output (splitdata)  
        VALUES(SUBSTRING(@string, @start, @end - @start)) 
        SET @start = @end + 1 
        SET @end = CHARINDEX(@delimiter, @string, @start)

    END 
    RETURN 
END

然后使用此查询

DECLARE @tbl TABLE (splt varchar(50))
 DECLARE   cur CURSOR FAST_FORWARD READ_ONLY
FOR SELECT  [CallTraversalLog]
FROM  [dbo].[Sample_Table]   --replace with your table
DECLARE @i varchar(50)
OPEN cur
FETCH NEXT FROM cur INTO @i

WHILE @@FETCH_STATUS=0
BEGIN
INSERT INTO @tbl

SELECT DISTINCT * FROM  [fnSplitString]  ( @i,';') as splt

FETCH NEXT FROM cur INTO @i 
END
CLOSE cur
DEALLOCATE cur
SELECT DISTINCT * FROM @tbl

enter image description here

答案 3 :(得分:0)

我编写了一个测试脚本,该脚本会遍历每一行,并将分隔的段分解为临时@results表。对于这样一个简单的任务来说似乎有点费解,可以通过c#/ linq中的几行来实现。这就是SQL。

希望代码和评论充分解释它是如何工作的。

-- SETUP TEST ENVIROMENT
declare @csvTest as table
(VAL nvarchar(100))

declare @result as table
(VAL nvarchar(15))

INSERT INTO @csvTest (VAL)
VALUES('CUSTSEG;PHPM;PBLOGIN;PBMENU;PBBI;PBLST3')

INSERT INTO @csvTest (VAL)
VALUES('CUSTSEG;CARDMNU;CRC;CRCBISOA;CRCBI')

INSERT INTO @csvTest (VAL)
VALUES('ONLYONE')

DECLARE @sCsv nvarchar(100)


-- Loop through each row using a cursor
DECLARE CSV_CURSOR cursor FOR 
select VAL
from @csvTest

OPEN CSV_CURSOR

FETCH NEXT FROM CSV_CURSOR 
INTO @sCsv

WHILE (@@FETCH_STATUS = 0)
Begin
    declare @start int
    declare @len int
    Set @start = 0
    DECLARE @segment nvarchar(15)

    while 1=1
    Begin
        set @segment = ''

        -- check if there are any delimiters left
        if CHARINDEX(';', @sCsv) = 0
            Set @segment = @sCsv
        else
            select @segment = SUBSTRING(@sCsv, 0, CHARINDEX(';', @sCsv))

        -- insert the segment if not already inserted
        insert into @result (VAL)
        select @segment
        where not exists (select * from @result where VAL = @segment)
        and @segment != ''

        -- break if last
        if CHARINDEX(';', @sCsv) = 0
            break

        -- cut out the segment and continue
        Set @sCsv = SUBSTRING(@sCsv, @start + len(@segment) + 2, LEN(@sCsv) - len(@segment) - 1)
    End

    -- get next row
    FETCH NEXT FROM CSV_CURSOR 
    INTO @sCsv
End

CLOSE CSV_CURSOR
DEALLOCATE CSV_CURSOR

select * from @result 

答案 4 :(得分:0)

可以通过替换“&amp;”来实现查询在内部查询中使用一些关键字并在外部查询中返回原始。

我希望这项工作。

http://www.sqlfiddle.com/#!3/82d2c/5