Reputation: 59
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
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
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
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