How to split/denormalize a SQL table where one of the columns contains comma-separated values
CREATE FUNCTION [dbo].[SplitCSV]
(@CSV VARCHAR (MAX))
RETURNS @OutTable TABLE ([ID] VARCHAR (255) NOT NULL)
AS BEGIN
--hold the current cursor position
declare @currentposition int
--hold the next position index of the cursor.
declare @nextposition int
--the length of the string split section
declare @lengthOfString int
--Assign the starting position of the string
SET @currentposition = 0
--The reason for this is to force entrance into the while loop below.
SET @nextposition = 1
WHILE @nextposition > 0
BEGIN
-- Assign the next position to be the current index of ‘,’ + 1
SELECT @nextposition = charindex(',', @CSV, @currentposition + 1)
-- In here we need to find 2 things. The position of the ‘,’
-- and the length of the string segment in between.
SELECT @lengthOfString = CASE
WHEN @nextposition > 0 THEN @nextposition - @currentposition
ELSE len(@CSV) + 1
END
-- @currentposition - 1
--After the length and position is found all we need to do
--is take the substring of the string passed in.
IF (
(substring(@CSV, @currentposition + 1, @lengthOfString-1) <> '')
AND (substring(@CSV, @currentposition + 1, @lengthOfString-1) IS NOT NULL)
) BEGIN
INSERT @OutTable (ID) VALUES (substring(@CSV, @currentposition + 1, @lengthOfString-1))
END
--Set the current position to the next position
SELECT @currentposition = @nextposition
END
RETURN
END
GO
Turning a Comma Separated string into individual rows
| SomeID | OtherID | Data
+----------------+-------------+-------------------
| abcdef-..... | cdef123-... | 18,20,22
| abcdef-..... | 4554a24-... | 17,19
| 987654-..... | 12324a2-... | 13,19,20
to
| SomeID | OtherID | Data
+----------------+-------------+-------------------
| abcdef-..... | cdef123-... | 18
| abcdef-..... | cdef123-... | 20
| abcdef-..... | cdef123-... | 22
| abcdef-..... | 4554a24-... | 17
| abcdef-..... | 4554a24-... | 19
| 987654-..... | 12324a2-... | 13
| 987654-..... | 12324a2-... | 19
| 987654-..... | 12324a2-... | 20
-- Create dummy tagble
CREATE TABLE Testdata
(
SomeID INT,
OtherID INT,
String VARCHAR(MAX)
)
INSERT Testdata SELECT 1, 9, '18,20,22'
INSERT Testdata SELECT 2, 8, '17,19'
INSERT Testdata SELECT 3, 7, '13,19,20'
INSERT Testdata SELECT 4, 6, ''
INSERT Testdata SELECT 9, 11, '1,2,3,4'
-- Query
;WITH tmp(SomeID, OtherID, DataItem, String) AS
(
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM Testdata
UNION all
SELECT
SomeID,
OtherID,
LEFT(String, CHARINDEX(',', String + ',') - 1),
STUFF(String, 1, CHARINDEX(',', String + ','), '')
FROM tmp
WHERE String > ''
)
SELECT
SomeID,
OtherID,
DataItem
FROM tmp
ORDER BY SomeID
-- OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
Output
SomeID | OtherID | DataItem
--------+---------+----------
1 | 9 | 18
1 | 9 | 20
1 | 9 | 22
2 | 8 | 17
2 | 8 | 19
3 | 7 | 13
3 | 7 | 19
3 | 7 | 20
4 | 6 |
9 | 11 | 1
9 | 11 | 2
9 | 11 | 3
9 | 11 | 4