Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Server split a single column multiple times

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

Table

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

Output Desired:

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.

like image 242
Michael Avatar asked Jun 22 '11 13:06

Michael


People also ask

How do I split one column data into multiple columns in SQL?

sql. functions provide a function split() which is used to split DataFrame string Column into multiple columns.

How do I split a column in SQL?

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.

How split a column with delimited string into multiple columns in SQL Server?

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.

How do I split a column into a row in SQL?

We can convert rows into column using PIVOT function in SQL.


3 Answers

-- 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
like image 191
Mikael Eriksson Avatar answered Oct 17 '22 02:10

Mikael Eriksson


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.

like image 34
Yuck Avatar answered Oct 17 '22 02:10

Yuck


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
*/
like image 1
leoinfo Avatar answered Oct 17 '22 02:10

leoinfo