Nitin.Gawade
Nitin.Gawade

Reputation: 59

We need to split data for two columns in sql server

We need to split data for two columns in sql server.

CREATE TABLE #t (Id int,Number varchar(32),Name varchar(32));    

Insert Into #t(Id, Number,Name)
Values(123, '1,2,3','a~b~c') ,
(456, '1,2,3,4','a~b~c~d'),
(789, '1,2','a~b')

SELECT *FROM #t 

Input:

Id  Number  Name
123 1,2,3   a~b~c
456 1,2,3,4 a~b~c~d
789 1,2 a~b

We need output in below format:

Id  Number  Name
123 1   a
123 2   b
123 3   c
456 1   a
456 2   b
456 3   c
456 4   d
789 1   a
789 2   b

Upvotes: 2

Views: 176

Answers (3)

gofr1
gofr1

Reputation: 15977

Another way with XML and CTEs:

DECLARE @x xml

SELECT @x = (
    SELECT  Id as '@id',
            CAST('<n number="'+REPLACE(Number,',','"/><n number="')+'"/>' as xml),
            CAST('<w name="'+REPLACE([Name],'~','"/><w name="')+'" />' as xml)
    FROM #t 
    FOR XML PATH('i')
)

;WITH numbers AS (
SELECT  t.v.value('../@id','int') as id,
        t.v.value('@number','nvarchar(10)') as number,
        ROW_NUMBER() OVER (PARTITION BY t.v.value('../@id','int') ORDER BY (SELECT NULL)) as RN
FROM @x.nodes('/i/n') as t(v)
), names AS (
SELECT  t.v.value('../@id','int') as id,
        t.v.value('@name','nvarchar(10)') as [name],
        ROW_NUMBER() OVER (PARTITION BY t.v.value('../@id','int') ORDER BY (SELECT NULL)) as RN
FROM @x.nodes('/i/w') as t(v)
)

SELECT  n.id,
        n.number,
        nn.[name]
FROM numbers n
INNER JOIN names nn
    ON n.id = nn.id and n.rn = nn.rn

Output:

id  number  name
123 1       a
123 2       b
123 3       c
456 1       a
456 2       b
456 3       c
456 4       d
789 1       a
789 2       b

I use SELECT NULL in ROW_NUMBER() part to get ordering as is. But it can cause some problems like not right ordering.

Upvotes: 0

Unnikrishnan R
Unnikrishnan R

Reputation: 5031

Here is the method using cte..

;WITH cte_1
AS
    (SELECT Id,ROW_NUMBER()OVER(PARTITION BY ID ORDER BY ID,LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))))RNO
    ,LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Number

    FROM
    (
    SELECT Id
           ,CAST('<XMLRoot><RowData>' + REPLACE(Number,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
    FROM   #t 
    )t
    CROSS APPLY x.nodes('/XMLRoot/RowData')m(n))
,cte_2
AS
    (SELECT Id,ROW_NUMBER()OVER(PARTITION BY ID ORDER BY ID,LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))))RNO
    ,LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Name

    FROM
    (
    SELECT Id,CAST('<XMLRoot><RowData>' + REPLACE(Name,'~','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
    FROM   #t 
    )t
    CROSS APPLY x.nodes('/XMLRoot/RowData')m(n))
SELECT a.id,a.Number,b.name
FROM cte_1 a JOIN cte_2 b
    ON a.id=b.id and a.RNo=b.RNo

Upvotes: 1

John Cappelletti
John Cappelletti

Reputation: 81930

With the help of a parser and a Cross Apply

Select A.ID
      ,B.*
 From  #t A
 Cross Apply (  Select Number=C.RetVal
                      ,Name  =D.RetVal
                  From (Select * from [dbo].[udf-Str-Parse](A.Number,',')) C
                  Join (Select * from [dbo].[udf-Str-Parse](A.Name,'~')) D
                    on (C.RetSeq=D.RetSeq)
             ) B

Returns

ID  Number  Name
123 1       a
123 2       b
123 3       c
456 1       a
456 2       b
456 3       c
456 4       d
789 1       a
789 2       b

The UDF if needed

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

Edit If you Can't use the UDF - We can move the UDF code into the two sub-queries to produce the same results

Select A.ID
      ,B.*
 From  #t A
 Cross Apply (  Select Number=C.RetVal
                      ,Name  =D.RetVal
                  From (
                        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(A.Number,',','</x><x>')+'</x>' as xml).query('.')) as A 
                            Cross Apply x.nodes('x') AS B(i)
                        ) C
                  Join (
                        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(A.Name,'~','</x><x>')+'</x>' as xml).query('.')) as A 
                            Cross Apply x.nodes('x') AS B(i)
                        ) D
                    on (C.RetSeq=D.RetSeq)
             ) B

Upvotes: 0

Related Questions