I have a database table that has a column with stacked data with two levels with a column that I want to break a part. Here is the example of the data (data changed to protect the innocent :) :
ID = varchar(100)
CarData = varchar(1000)
ID CarData
1 Nissan:blue:20000,Ford:green:10000
2 Nissan:steel:20001,Ford:blue:10001,Chevy:blue:10000,Ford:olive:10000
** Note that cardata can is not fixed, and can have many cars in it
ID Manufacture Color Cost
1 Nissan Blue 20000
1 Ford green 10000
2 Nissan steel 20001
... and on
So to say it plainly I need to break the first stacked field which is a comma and create a row for that, then break the second stacked field which is a colon into columns.
Any help would be greatly appreciated.
sql. functions provide a function split() which is used to split DataFrame string Column into multiple columns.
Split comma-separated value string in a column. SELECT ProductId, Name, value FROM Product CROSS APPLY STRING_SPLIT(Tags, ','); Here is the result set. The order of the output may vary as the order is not guaranteed to match the order of the substrings in the input string.
You can do it using the following methods: Convert delimited string into XML, use XQuery to split the string, and save it into the table. Create a user-defined table-valued function to split the string and insert it into the table. Split the string using STRING_SPLIT function and insert the output into a table.
We can convert rows into column using PIVOT function in SQL.
-- Sample data
declare @T table(ID int, CarData varchar(100))
insert into @T values
(1, 'Nissan:blue:20000,Ford:green:10000'),
(2, 'Nissan:steel:20001,Ford:blue:10001,Chevy:blue:10000,Ford:olive:10000')
-- Recursice CTE to get one row for each car
;with cte(ID, Car, CarData) as
(
select ID,
cast(substring(CarData+',', 1, charindex(',', CarData+',')-1) as varchar(100)),
stuff(CarData, 1, charindex(',', CarData), '')+','
from @T
where len(CarData) > 0
union all
select ID,
cast(substring(CarData, 1, charindex(',', CarData)-1) as varchar(100)),
stuff(CarData, 1, charindex(',', CarData), '')
from cte
where len(CarData) > 0
)
-- Use parsename to split the car data
select ID,
parsename(replace(Car, ':', '.'), 3) as Manufacture,
parsename(replace(Car, ':', '.'), 2) as Color,
parsename(replace(Car, ':', '.'), 1) as Cost
from cte
order by ID
Result:
ID Manufacture Color Cost
-- ----------- ------ -----
1 Nissan blue 20000
1 Ford green 10000
2 Nissan steel 20001
2 Ford blue 10001
2 Chevy blue 10000
2 Ford olive 10000
Edit 1
You will have trouble with parsename
if color, cost or a manufacturer name contains a .
. If that is the case you should try this instead.
-- Sample data
declare @T table(ID int, CarData varchar(100))
insert into @T values
(1, 'Nissan:blue:20000,Ford:green:10000'),
(2, 'Nissan:steel:20001,Ford:blue:10001,Chevy:blue:10000,Ford:olive:10000')
-- Recursice CTE to get one row for each car
;with cte(ID, Car, CarData) as
(
select ID,
cast(substring(CarData+',', 1, charindex(',', CarData+',')-1) as varchar(100)),
stuff(CarData, 1, charindex(',', CarData), '')+','
from @T
where len(CarData) > 0
union all
select ID,
cast(substring(CarData, 1, charindex(',', CarData)-1) as varchar(100)),
stuff(CarData, 1, charindex(',', CarData), '')
from cte
where len(CarData) > 0
)
-- Split the car data with substring
select ID,
substring(Car, 1, P1.Pos-1) as Manufacture,
substring(Car, P1.Pos+1, P2.Pos-P1.Pos-1) as Color,
substring(Car, P2.Pos+1, len(Car)-P2.Pos) as Cost
from cte
cross apply (select charindex(':', Car)) as P1(Pos)
cross apply (select charindex(':', Car, P1.Pos+1)) as P2(Pos)
order by ID
Use this string splitting function to produce a table of results.
I would first call dbo.split()
using a ,
as the separator character. Then you'll have a list of items like:
Nissan:blue:20000
Ford:green:10000
Nissan:steel:20001
Ford:blue:10001
Chevy:blue:10000
Ford:olive:10000
From there you can call dbo.split()
again using :
as your separator. Each call will result in exactly three records (assuming your design as at least that "normal").
As @JNK mentioned in his comment, hopefully this is not something you'd want to be running regularly.
EDIT:
Some sample code to get you started:
SELECT *
INTO #YuckyCar
FROM (
SELECT 1 ID, 'Nissan:blue:20000,Ford:green:10000' CarData
UNION
SELECT 2, 'Nissan:steel:20001,Ford:blue:10001,Chevy:blue:10000,Ford:olive:10000'
) T;
-- Shows logical step #1
SELECT ID, X.items MoreCarData
FROM #YuckyCar CROSS APPLY dbo.Split(CarData, ',') X;
-- Shows logical step #2
SELECT Q.ID, Y.items
FROM (
SELECT ID, X.items MoreCarData
FROM #YuckyCar CROSS APPLY dbo.Split(CarData, ',') X) Q CROSS APPLY dbo.Split(Q.MoreCarData, ':') Y
DROP TABLE #YuckyCar;
The problem in the last part is that you can't guarantee row 1 = Manufacturer, row 2 = Color, row 3 = Cost.
This should solve your problem:
[EDIT] Your ID is a varchar(100) and you do not specify if it is a primary key, so I made some changes ... ID does't have to be primary key in this case.
declare @T table(ID varchar(100), CarData varchar(1000))
declare @OUT table(pk INT IDENTITY(1,1), ID varchar(100), Manufacture varchar(100), Color VARCHAR(100), Cost INT)
insert into @T (ID, CarData) values
('1', 'Nissan:blue:20000,Ford:green:10000'),
('2', 'Nissan:steel:20001,Ford:blue:10001,Chevy:blue:10000,Ford:olive:10000')
DECLARE @x XML, @i INT, @ID VARCHAR(100), @maxi INT;
;WITH list AS (SELECT pk = ROW_NUMBER() OVER(ORDER BY ID), * FROM @T)
SELECT @i=1, @maxi=MAX(pk) FROM list;
WHILE @i <= @maxi
BEGIN
;WITH list AS (SELECT pk = ROW_NUMBER() OVER(ORDER BY ID), * FROM @T)
SELECT
@x = CAST( '<root><car><prop>' +
REPLACE(
REPLACE(
CarData
,':'
,'</prop><prop>'
)
,','
,'</prop></car><car><prop>'
) +
'</prop></car></root>'
AS XML)
, @ID = ID
FROM list
WHERE pk = @i
INSERT INTO @OUT
SELECT
ID = @ID
,Manufacture = x.value('./prop[1]','VARCHAR(100)')
,Color = x.value('./prop[2]','VARCHAR(100)')
,Cost = x.value('./prop[3]','INT')
FROM @x.nodes('/root/car') AS T(x)
SET @i = @i + 1;
END
SELECT * FROM @OUT
/* -- OUTPUT
ID Manufacture Color Cost
--------------------------------
1 Nissan blue 20000
1 Ford green 10000
2 Nissan steel 20001
2 Ford blue 10001
2 Chevy blue 10000
2 Ford olive 10000
*/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With