问题
I have two sql columns each with delimited data that I want to collate and combine into a single delimited column. The number of items in the column is variable for each row. However there will always be a matching number of items between the two columns of each row. For Example...
*******************************
ORIGINAL SQL TABLE
*******************************
value * unit
*******************************
4 ; 5 * mg ; kg
50 * mg
7.5 ; 325 * kg ; mg
100 ; 1.5 ; 50 * mg ; g ; mg
********************************
*********************************
DESIRED SQL RESULT
*********************************
value-unit
*********************************
4 mg; 5 kg
50 mg
7.5 kg; 325 mg
100 mg; 1.5 g; 50 mg
*********************************
How do I do this with T-SQL? I'm using SQL Server 2012
回答1:
Using only common table expressions, we can get to the required results too, as below:-
First lets set up the data
declare @original table(
[value] varchar(250),
[unit] varchar(250)
)
insert into @original values
('4 ; 5','mg ; kg '),
('50','mg ' ),
('7.5 ; 325','kg ; mg ' ),
('100 ; 1.5 ; 50 ','mg ; g ; mg' )
Now, lets build the common table expressions:-
;with cte as (
select o.[value]+';' [value],o.[unit]+';' [unit],row_number() over (ORDER BY (Select 0)) [row] from @original o
),cte2 as (
select *
,1 [ValueStart],CHARINDEX(';',[value]) [ValueEnd]
,1 [UnitStart],CHARINDEX(';',[unit]) [UnitEnd]
from cte
),cte3 as (
select * from cte2
union all
select [value],[unit],[row]
,[ValueEnd]+1 [ValueStart],CHARINDEX(';',[value],[ValueEnd]+1) [ValueEnd]
,[UnitEnd]+1 [UnitStart],CHARINDEX(';',[unit],[UnitEnd]+1) [UnitEnd]
from cte3 where [UnitEnd]>0
),cte4 as (
select *,row_number() over (partition by [row] order by [row]) [subRow]
, rtrim(ltrim(substring([unit],[UnitStart],[UnitEnd]-[UnitStart]))) [subUnit]
, rtrim(ltrim(substring([value],[ValueStart],[ValueEnd]-[ValueStart]))) [subValue]
from cte3
where [UnitEnd]>0
),cte5 as (
select subRow,[row],[subValue],[subUnit],cast([subValue]+' '+[subUnit] as varchar(max)) [ValueUnit] from cte4 where subRow=1
union all
select cte4.subRow,cte4.[row],cte4.[subUnit],cte4.[subValue]
,cte5.[ValueUnit]+';'+ cte4.[subValue]+' '+cte4.[subUnit] [ValueUnit]
from cte4
inner join cte5 on (cte5.subRow+1)=cte4.subRow and cte5.[row]=cte4.[row]
),cte6 as (
select *,row_number() over (partition by [row] order by subRow desc) [selected] from cte5
)
select ValueUnit from cte6
where [selected]=1
order by [row]
Results will be as below:-
ValueUnit
============
4 mg;5 kg
50 mg
7.5 kg;325 mg
100 mg;1.5 g;50 mg
回答2:
STRING_SPLIT is the most current solution. If you're not on 2016 or later and cannot set your db compatibility to that version or later, here is a more old fashioned approach using xml:
I added an Identity to your original table to have something to order by -
SELECT splitNumbers.splitNumber,
splitValues.splitValue,
splitNumbers.splitNumber + ' ' + splitValues.splitValue AS combined
FROM
(
SELECT --numbers,
LTRIM(RTRIM(m.n.value('.[1]', 'varchar(8000)'))) AS splitNumber,
ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM
(
SELECT id,
CAST('<XMLRoot><RowData>' + REPLACE(numbers, ' ; ', '</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS xmlNumbers
FROM #x
) fee
CROSS APPLY xmlNumbers.nodes('/XMLRoot/RowData') m(n)
) splitNumbers
INNER JOIN
(
SELECT LTRIM(RTRIM(m.v.value('.[1]', 'varchar(8000)'))) AS splitValue,
ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM
(
SELECT id,
CAST('<XMLRoot><RowData>' + REPLACE(units, ' ; ', '</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS xmlUnits
FROM #x
) fee
CROSS APPLY xmlUnits.nodes('/XMLRoot/RowData') m(v)
) splitValues
ON splitNumbers.rn = splitValues.rn;
This gives the following results:
================================
splitNumber splitValue combined
4 mg 4 mg
5 kg 5 kg
50 mg 50 mg
7.5 kg 7.5 kg
325 mg 325 mg
100 mg 100 mg
1.5 g 1.5 g
50 mg 50 mg
来源:https://stackoverflow.com/questions/55655510/combining-two-sql-columns-with-delimited-data-by-collating-into-a-single-column