Add rows to result set from comma delimited data - sql

I have a select statement returning data in the following format- (from a bad database design)
ID Numbers
3 6,7,8
and I need to tweak it to return
ID Number
3 6
3 7
3 8
to fix it. What's the best way to do this? I do not need a permanent function, I just need the result set from a select query to import into a new database.
thanks!

Since you are using SQL Server 2015, your best option is to write a UDF like so:
CREATE FUNCTION [dbo].[udfSplit]
(
#sInputList VARCHAR(8000) -- List of delimited items
,
#sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
)
RETURNS #List TABLE ( item VARCHAR(8000) )
BEGIN
DECLARE #sItem VARCHAR(8000);
WHILE CHARINDEX(#sDelimiter, #sInputList, 0) <> 0
BEGIN
SELECT #sItem = RTRIM(LTRIM(SUBSTRING(#sInputList, 1,
CHARINDEX(#sDelimiter,
#sInputList, 0)
- 1))) ,
#sInputList = RTRIM(LTRIM(SUBSTRING(#sInputList,
CHARINDEX(#sDelimiter,
#sInputList, 0)
+ LEN(#sDelimiter),
LEN(#sInputList))));
IF LEN(#sItem) > 0
INSERT INTO #List
SELECT #sItem;
END;
IF LEN(#sInputList) > 0
INSERT INTO #List
SELECT #sInputList; -- Put the last item in
RETURN;
END;
And use it like so:
SELECT ID ,
item
FROM ( SELECT 3 AS ID ,
'6,7,8' AS Numbers
) x
CROSS APPLY udfSplit(x.Numbers, ',');

Try this:
DECLARE #YourTable Table (
ID INT IDENTITY(1,1)
, Number VARCHAR(10)
)
INSERT INTO #YourTable
VALUES
('1,2,3')
, ('4,5,6,7')
, ('8,9')
, ('10')
;WITH CTE
AS
(
SELECT 1 AS ID
UNION ALL
SELECT ID + 1 FROM CTE
WHERE ID < 100
)
SELECT
x.ID
, SUBSTRING(x.Number, t.ID, CHARINDEX(',', x.Number + ',', t.ID) - t.ID) AS Number
FROM #YourTable x
JOIN CTE t
ON t.ID <= DATALENGTH(x.Number)+1
AND SUBSTRING(',' + x.Number, t.ID, 1) = ','
ORDER BY ID, Number
More information can be found here: http://www.sqlservercentral.com/articles/Tally+Table/72993/

Related

Split string and number columns

Let's say I have a table such as
ItemID ClassID
------------------------
1 10, 13, 12
2 5, 7
and would like to copy the data to another table like so
ItemID Numbering ClassID
----------------------------------
1 1 10
1 2 13
1 3 12
2 1 5
2 2 7
Separating the comma-delimited ClassID field into individual rows, retaining the order they had in the first table
Populating the Numbering row on insert. The Numbering column has sequential integers for each batch of ClassID and is why ClassID needs to be kept in order.
I have attempted this with the following function:
CREATE FUNCTION dbo.Split
(
#String NVARCHAR(MAX)
)
RETURNS #SplittedValues TABLE(
Value INT
)
AS
BEGIN
DECLARE #SplitLength INT
DECLARE #Delimiter VARCHAR(10)
SET #Delimiter = ','
WHILE len(#String) > 0
BEGIN
SELECT #SplitLength = (CASE charindex(#Delimiter, #String)
WHEN 0 THEN
datalength(#String) / 2
ELSE
charindex(#Delimiter, #String) - 1
END)
INSERT INTO #SplittedValues
SELECT cast(substring(#String, 1, #SplitLength) AS INTEGER)
WHERE
ltrim(rtrim(isnull(substring(#String, 1, #SplitLength), ''))) <> '';
SELECT #String = (CASE ((datalength(#String) / 2) - #SplitLength)
WHEN 0 THEN
''
ELSE
right(#String, (datalength(#String) / 2) - #SplitLength - 1)
END)
END
RETURN
END
but it only partly works. It copies the rows the correct amount of times (i.e. three times for ItemID=1, and twice for ItemID=2 in the above example), but they are exact copies of the row (all saying '10, 13, 12') and the comma-delimited parts are not split up. There is also nothing in the function to add to the Numbering column.
So, I have two questions: How do I modify the above function to split up the ClassID string, and what do I add to correctly increment the Numbering column?
Thanks!
I'd use a recursive CTE to do it.
WITH SplitCTE AS
(
SELECT
itemid,
LEFT(ClassID,CHARINDEX(',',ClassID)-1) AS ClassID
,RIGHT(ClassID,LEN(ClassID)-CHARINDEX(',',ClassID)) AS remaining
FROM table1
WHERE ClassID IS NOT NULL AND CHARINDEX(',',ClassID)>0
UNION ALL
SELECT
itemid,
LEFT(remaining,CHARINDEX(',',remaining)-1)
,RIGHT(remaining,LEN(remaining)-CHARINDEX(',',remaining))
FROM SplitCTE
WHERE remaining IS NOT NULL AND CHARINDEX(',',remaining)>0
UNION ALL
SELECT
itemid,remaining,null
FROM SplitCTE
WHERE remaining IS NOT NULL AND CHARINDEX(',',remaining)=0
)
SELECT
itemid,
row_number() over (partition by itemid order by cast(classid as int) asc) as Numbering,
cast (ClassID as int) as ClassID
FROM
SplitCTE
UNION ALL
select
ItemId,
1,
cast(classid as int)
FROM table1
WHERE ClassID IS NOT NULL AND CHARINDEX(',',ClassID) = 0
SQL Fiddle
DECLARE #t TABLE( ID INT IDENTITY, data VARCHAR(50))
INSERT INTO #t(data) SELECT '10, 13, 12'
INSERT INTO #t(data) SELECT '5, 7'
select F1.id,O.splitdata, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY (SELECT 1))
from (
select *,cast(''+replace(F.data,',','')+'' as XML) as xmlfilter from #t F
)F1
cross apply
(
select fdata.D.value('.','varchar(50)') as splitdata from f1.xmlfilter.nodes('X') as fdata(D)
) O

Stored Procedure to Insert comma seperated values as multiple records

Please help me in creating a stored procedure which accepts comma separated values and inserts as multiple rows.
So one parameter #Name will contain values A,B,C and the other parameter #Id will contain values as 1,2,3
The table values after insertion should be as below:
Name Id
------------
A 1
A 2
A 3
B 1
B 2
B 3
C 1
C 2
C 3
How can I write a stored procedure that can insert the comma-separated values as shown above. Also, If the table already consists of a Name,id pair for example, if A,2 is already there in the table, then it should not insert.
I am using SQL Server 2005. Thanks in advance.
Something like this?
DECLARE #var1 VARCHAR(100)='A,B,C';
DECLARE #var2 VARCHAR(100)='1,2,3';
WITH rep1(name, delim) AS
(
SELECT #var1 name, ',' delim
UNION ALL
SELECT LEFT(name, CHARINDEX(delim, name, 1) - 1) name, delim
FROM rep1
WHERE (CHARINDEX(delim, name, 1) > 0)
UNION ALL
SELECT RIGHT(name, LEN(name) - CHARINDEX(delim, name, 1)) name, delim
FROM rep1
WHERE (CHARINDEX(delim, name, 1) > 0)
)
,rep2(id, delim) AS
(
SELECT #var2 id, ',' delim
UNION ALL
SELECT LEFT(id, CHARINDEX(delim, id, 1) - 1) id, delim
FROM rep2
WHERE (CHARINDEX(delim, id, 1) > 0)
UNION ALL
SELECT RIGHT(id, LEN(id) - CHARINDEX(delim, id, 1)) id, delim
FROM rep2
WHERE (CHARINDEX(delim, id, 1) > 0)
)
INSERT #table
(Name
,ID)
SELECT
r1.name
,r2.id
FROM rep1 r1
CROSS JOIN rep2 r2
LEFT JOIN #table t
ON r2.id=t.id
AND t.name=r1.name
WHERE (CHARINDEX(r1.delim, r1.name, 1) = 0)
AND (CHARINDEX(r2.delim, r2.id, 1) = 0)
AND t.name IS NULL
ORDER BY r1.name
,r2.id
OPTION (MAXRECURSION 0);
Here we are sepearting Comma Seperated into rows
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
IF OBJECT_ID('tempdb..#NewTemp') IS NOT NULL
DROP TABLE #NewTemp
Declare #Testdata table ( name Varchar(max), Data varchar(max))
insert #Testdata select 'A', '1,2,3'
insert #Testdata select 'B', '1,2,3'
insert #Testdata select 'C', '1,2'
insert #Testdata select 'A', '1,2,3,4'
insert #Testdata select 'C', '1,2,3,4,5'
;with tmp(name, DataItem, Data) as (
select name, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from #Testdata
union all
select name, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from tmp
where Data > ''
)
Then Inserting into Temp Table
select DISTINCT name, DataItem INTO #Temp
from tmp WHERE EXISTS (Select DISTINCT name,DataItem from tmp)
order by name
Here we are controlling entry of Duplicates we can observe combination won't repeat like (A,1),(B,1)Even though they are multiple
CREATE TABLE #NewTemp(name Varchar(max), Data varchar(max))
INSERT INTO #NewTemp (name,Data)
Select name,DataItem from #Temp
Select * FROM #NewTemp
You can go and create one user defined functions for splitting the comma separated values into rows as below
How this function will work and more on it can be found here
CREATE FUNCTION dbo.Split
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
DECLARE #index INT
SET #index = Charindex(#SplitOn,#RowData)
While (#index>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,#index-1)))
Set #RowData = Substring(#RowData,#index+1,len(#RowData))
Set #Cnt = #Cnt + 1
SET #index = Charindex(#SplitOn,#RowData)
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
Once this function is created, you can use it for your requirement as below
declare #Name VARCHAR(30)
declare #Id VARCHAR(30)
SET #Name = 'A,B,C'
SET #Id = '1,2,3'
select A.Data,B.Data FROM dbo.Split(#name,',') A ,dbo.Split(#id,',') B

SQL Query for Min and Max Values [duplicate]

This question already has answers here:
How to split a comma-separated value to columns
(38 answers)
Closed 8 years ago.
I have the following data in a table. The number of values in each row can vary and the number of rows could also vary.
The table has 1 column with csv formatted values. The values will always be numeric
Data
1,2
4
5,12, 10
6,7,8,9,10
15,17
I would like to end up with a temp table with the following
Data Lowest Highest
1,2 1 2
4 4 4
5,12, 10 5 12
6,7,8,9,10 6 10
15,17 15 17
Can anyone help with writing a sql query or function to achieve this
Instead of function, you can achieve by this
;WITH tmp
AS (SELECT A.rn,split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT Row_number() OVER(ORDER BY (SELECT NULL)) AS RN,
Cast ('<M>' + Replace([data], ',', '</M><M>') + '</M>' AS XML) AS String
FROM table1) AS A
CROSS apply string.nodes ('/M') AS Split(a))
SELECT X.data,Tmp.lower,Tmp.higher
FROM (SELECT rn,Min(Cast(string AS INT)) AS Lower,Max(Cast(string AS INT)) AS Higher
FROM tmp
GROUP BY rn) Tmp
JOIN (SELECT Row_number() OVER(ORDER BY (SELECT NULL)) AS RN1,data
FROM table1) X
ON X.rn1 = Tmp.rn
FIDDLE DEMO
Output would be:
Data Lower Higher
1,2 1 2
4 4 4
5,12, 10 5 12
6,7,8,9,10 6 10
15,17 15 17
First create a user defined function to convert each row of 'DATA' column to a intermediate table as:
/****** Object: UserDefinedFunction [dbo].[CSVToTable]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CSVToTable] (#InStr VARCHAR(MAX))
RETURNS #TempTab TABLE
(id int not null)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
Function is explained further here.
Then Using Cross Apply we can get the desired output as:
With CTE as
(
select
T.Data, Min(udf.Id) as [Lowest],Max(udf.Id) as [Highest]
from
Test T
CROSS APPLY dbo.CSVToTable(T.Data) udf
Group By Data
)
Select * from CTE
Sample Code here...
What a Cross Apply does is : it applies the right table expression to each row from the left table and produces a result table with the unified result sets.
Create table #temp1 (name varchar(100),value int )
Declare #len int
Select #len=(select max(LEN(name)-LEN(replace(name,',',''))) from table)
Declare #i int = 1
while (#i<=#len+1)
begin
insert into #temp1
select name,PARSENAME(REPLACE(name,',','.'),#i) from table t
set #i = #i+1
end
Select name,MIN(value) MINV,MAX(value) MAXV from #temp1 group by name
declare #Testdata table ( Data varchar(max))
insert #Testdata select '1,2'
insert #Testdata select '4'
insert #Testdata select '5,12, 10'
insert #Testdata select '6,7,8,9,10'
;with tmp( DataItem, Data, RN1) as (
select LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), ''),
ROW_NUMBER()OVER(ORDER BY (SELECT NULL))AS RN1
from #Testdata
union all
select LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), ''),RN1
from tmp
where Data > ''
)
Select x.data,t.Low,t.Up FROM
(Select RN1,MIN(Cast(DataItem AS INT)) As Low,
MAX(Cast(DataItem AS INT)) As Up
FROM tmp t GROUP BY t.RN1)t
JOIN (Select ROW_NUMBER()OVER(ORDER BY (SELECT NULL))AS RN,data from #Testdata)X
ON X.RN = t.RN1

T-SQL LIKE condition on comma-separated list

Is it possible to write a LIKE condition in T-SQL to match a comma-separated list which includes wildcards to a string. Let me explain further with an example:
Say you have the following command separated list of urls in a field:
'/, /news/%, /about/'
Now here's some examples of strings I'd like to match with the string above:
'/'
'/news/'
'/news/2/'
'/about/'
And here's some strings which would not match:
'/contact/'
'/about/me/'
I've achieved this in the past by writing a split function and then doing a like on each one. However I'm trying to get my query to work in SQL Server CE which doesn't support functions.
In case you are wondering here's how I achieved it using the split function:
SELECT Widgets.Id
FROM Widgets
WHERE (SELECT COUNT(*) FROM [dbo].[Split](Urls, ',') WHERE #Input LIKE Data) > 0
And here's the split function:
CREATE FUNCTION [dbo].[Split]
(
#RowData NVARCHAR(MAX),
#Separator NVARCHAR(MAX)
)
RETURNS #RtnValue TABLE
(
[Id] INT IDENTITY(1,1),
[Data] NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #Iterator INT
SET #Iterator = 1
DECLARE #FoundIndex INT
SET #FoundIndex = CHARINDEX(#Separator, #RowData)
WHILE (#FoundIndex > 0)
BEGIN
INSERT INTO #RtnValue ([Data])
SELECT Data = LTRIM(RTRIM(SUBSTRING(#RowData, 1, #FoundIndex - 1)))
SET #RowData = SUBSTRING(#RowData, #FoundIndex + DATALENGTH(#Separator) / 2, LEN(#RowData))
SET #Iterator = #Iterator + 1
SET #FoundIndex = CHARINDEX(#Separator, #RowData)
END
INSERT INTO #RtnValue ([Data])
SELECT Data = LTRIM(RTRIM(#RowData))
RETURN
END
I'd appreciate it if someone could help. Thanks
I can think of several options:
Use a session-keyed table: delete rows matching current spid, insert desired rows with current spid, read from table in SP, delete from table (again).
Make your client submit a query with many OR ... LIKE ... clauses.
Write an SP that does the same thing as your function and returns a recordset. INSERT YourTable EXEC SP #Strings and you are done!
Use the numbers-table-charindex-into-string inside of a derived table method of splitting the string.
Example
Let me flesh this out a little for you with an example combining ideas #3 and #4. Of course, your code for your function could be adapted, too.
Build a separate Numbers table. Here is example creation script:
--Numbers Table with 8192 elements (keeping it small for CE)
CREATE TABLE Numbers (
N smallint NOT NULL CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED
);
INSERT Numbers VALUES (1);
WHILE ##RowCount < 4096
INSERT Numbers SELECT N + (SELECT Max(N) FROM Numbers) FROM Numbers;
The SP:
CREATE PROCEDURE dbo.StringSplitRowset
#String varchar(8000)
AS
SELECT Substring(#String, l.StartPos, l.Chars) Item
FROM (
SELECT
S.StartPos,
IsNull(NullIf(CharIndex(',', #String, S.StartPos), 0) - S.StartPos, 8000)
FROM (
SELECT 1 UNION ALL
SELECT N.N + 1 FROM Numbers N WHERE Substring(#String, N.N, 1) = ','
) S (StartPos)
) L (StartPos, Chars);
And usage, easy as pie:
DECLARE #String varchar(8000);
SET #String = 'abc,def,ghi,jkl';
CREATE TABLE #Split (S varchar(8000));
INSERT #Split EXEC dbo.StringSplitRowset #String;
SELECT * FROM #Split;
Result:
abc
def
ghi
jkl
And finally, if you don't want to build a numbers table, you can use this SP. I think you will find that one of these two SPs performs well enough for you. There are other implementations of string splitting that could work as well.
ALTER PROCEDURE dbo.StringSplitRowset
#String varchar(8000)
AS
SELECT Substring(#String, l.StartPos, l.Chars) Item
FROM (
SELECT
S.StartPos,
IsNull(NullIf(CharIndex(',', #String, S.StartPos), 0) - S.StartPos, 8000)
FROM (
SELECT 1 UNION ALL
SELECT N.N + 1
FROM (
SELECT A.A * 4096 + B.B * 1024 + C.C * 256 + D.D * 64 + E.E * 16 + F.F * 4 + G.G N
FROM
(SELECT 0 UNION ALL SELECT 1) A (A),
(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) G (G),
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) F (F),
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) E (E),
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) D (D),
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) C (C),
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) B (B)
) N (N)
WHERE Substring(#String, N.N, 1) = ','
) S (StartPos)
) L (StartPos, Chars)
Any SQL writer serious about understanding some of the performance implications of splitting strings different ways ought to see Aaron Bertrand's blog post on splitting strings.
Also, any serious SQL Server database student ought to see Erland Sommarskog's How to Share Data between Stored Procedures.
Will SQL Server CE let you split with XML functions and use CROSS APPLY? If so, you could do something like this:
SELECT DISTINCT T1.id
FROM (
SELECT id, CAST(('<X>'+
REPLACE(REPLACE(urls,' ',''),',','</X><X>')+
'</X>'
) AS xml
) as URLsXML
FROM dbo.Widgets
) AS T1
CROSS APPLY(
SELECT N.value('.', 'varchar(50)') AS URLPattern
FROM URLsXML.nodes('X') AS S(N)
) AS T2
WHERE #Input LIKE T2.URLPattern
UPDATE: I just checked. It looks like SQL Server CE doesn't support the XML data type or CROSS APPLY.
I think you will have to populate another table with ID's and Patterns to join against.

Parsing text to multiple columns

I have a feed that is populating a single text field in a table with statistics.
I need to pull this data into multiple fields in another table
but the strange format makes importing automatically difficult.
The file format is flat text but an example is below:
08:34:52 Checksum=180957248,TicketType=6,InitialUserType=G,InitialUserID=520,CommunicationType=Incoming,Date=26-03-2012,Time=08:35:00,Service=ST,Duration=00:00:14,Cost=0.12
Effectively it's made up of:
[timestamp] [Field1 name]=[Field1 value],[Field2 name]=[Field2 value],[Field4 name]=[Field4 value]...[CR]
All fields are always in the same order but not always present.
Total columns could be anywhere from 5 to 30.
I've tried the below function to translate it which seems to work mostly but seems to randomly skip fields:
Parsing the data:
(SELECT [Data].[dbo].[GetFromTextString] ( 'Checksum=' ,',' ,RAWTEXT)) AS RowCheckSum,
(SELECT [Data].[dbo].[GetFromTextString] ( 'TicketType=' ,',' ,RAWTEXT)) AS TicketType,
And the Function:
CREATE FUNCTION [dbo].[GetFromTextString]
-- Input start and end and return value.
(#uniqueprefix VARCHAR(100),
#commonsuffix VARCHAR(100),
#datastring VARCHAR(MAX) )
RETURNS VARCHAR(MAX) -- Picked Value.
AS
BEGIN
DECLARE #ADJLEN INT = LEN(#uniqueprefix)
SET #datastring = #datastring + #commonsuffix
RETURN (
CASE WHEN (CHARINDEX(#uniqueprefix,#datastring) > 0)
AND (CHARINDEX(#uniqueprefix + #commonsuffix,#datastring) = 0)
THEN SUBSTRING(#datastring, PATINDEX('%' + #uniqueprefix + '%',#datastring)+#ADJLEN, CHARINDEX(#commonsuffix,#datastring,PATINDEX('%' + #uniqueprefix + '%',#datastring))- PATINDEX('%' + #uniqueprefix + '%',#datastring)-#ADJLEN) ELSE NULL END
)
END
Could anyone suggest a better/cleaner way to strip out the data or could someone work out why this formula skips rows?
Any help really appreciated.
NOTE - THE FIRST SOLUTION IS RUBBISH. I HAVE LEFT IN IT FOR HISTORICAL REASONS, BUT A BETTER SOLUTION IS CONTAINED BELOW
I am not even sure if this will be faster than your current method, but it is the way I would approach the issue (If i was forced into an SQL only solution). The first thing that is required is a table valued function that will perform a split function:
CREATE FUNCTION dbo.Split (#TextToSplit VARCHAR(MAX), #Delimiter VARCHAR(MAX))
RETURNS #Values TABLE (Position INT IDENTITY(1, 1) NOT NULL, TextValues VARCHAR(MAX) NOT NULL)
AS
BEGIN
WHILE CHARINDEX(#Delimiter, #TextToSplit) > 0
BEGIN
INSERT #Values
SELECT LEFT(#TextToSplit, CHARINDEX(#Delimiter, #TextToSplit) - 1)
SET #TextToSplit = SUBSTRING(#TextToSplit, CHARINDEX(#Delimiter, #TextToSplit) + 1, LEN(#TextToSplit))
END
INSERT #Values VALUES (#TextToSplit)
RETURN
END
For my example I am working from a temp table #Worklist, you may need to adapt yours accordingly, or you could just insert the relevant data into #Worklist where I have used dummy data:
DECLARE #WorkList TABLE (ID INT IDENTITY(1, 1) NOT NULL, TextField VARCHAR(MAX))
INSERT #WorkList
SELECT '08:34:52 Checksum=180957248,TicketType=6,InitialUserType=G,InitialUserID=520,CommunicationType=Incoming,Date=26-03-2012,Time=08:35:00,Service=ST,Duration=00:00:14,Cost=0.12'
UNION
SELECT '08:34:52 Checksum=180957249,TicketType=5,InitialUserType=H,InitialUserID=521,CommunicationType=Outgoing,Date=27-03-2012,Time=14:27:00,Service=ST,Duration=00:15:12,Cost=0.37'
The main bit of the query is done here. It is quite long, so I have tried to comment it as well as possible. If further clarification is required I can add more comments.
DECLARE #Output TABLE (ID INT IDENTITY(1, 1) NOT NULL, TextField VARCHAR(MAX))
DECLARE #KeyPairs TABLE (WorkListID INT NOT NULL, KeyField VARCHAR(MAX), ValueField VARCHAR(MAX))
-- STORE TIMESTAMP DATA - THIS ASSUMES THE FIRST SPACE IS THE END OF THE TIMESTAMP
INSERT #KeyPairs
SELECT ID, 'TimeStamp', LEFT(TextField, CHARINDEX(' ', TextField))
FROM #WorkList
-- CLEAR THE TIMESTAMP FROM THE WORKLIST
UPDATE #WorkList
SET TextField = SUBSTRING(TextField, CHARINDEX(' ', TextField) + 1, LEN(TextField))
DECLARE #ID INT = (SELECT MIN(ID) FROM #WorkList)
WHILE #ID IS NOT NULL
BEGIN
-- SPLIT THE STRING FIRST INTO ALL THE PAIRS (e.g. Checksum=180957248)
INSERT #Output
SELECT TextValues
FROM dbo.Split((SELECT TextField FROM #WorkList WHERE ID = #ID), ',')
DECLARE #ID2 INT = (SELECT MIN(ID) FROM #Output)
-- FOR ALL THE PAIRS SPLIT THEM INTO A KEY AND A VALUE (USING THE POSITION OF THE SPLIT FUNCTION)
WHILE #ID2 IS NOT NULL
BEGIN
INSERT #KeyPairs
SELECT #ID,
MAX(CASE WHEN Position = 1 THEN TextValues ELSE '' END),
MAX(CASE WHEN Position = 2 THEN TextValues ELSE '' END)
FROM dbo.Split((SELECT TextField FROM #Output WHERE ID = #ID2), '=')
DELETE #Output
WHERE ID = #ID2
SET #ID2 = (SELECT MIN(ID) FROM #Output)
END
DELETE #WorkList
WHERE ID = #ID
SET #ID = (SELECT MIN(ID) FROM #WorkList)
END
-- WE NOW HAVE A TABLE CONTAINING EAV MODEL STYLE DATA. THIS NEEDS TO BE PIVOTED INTO THE CORRECT FORMAT
-- ENSURE COLUMNS ARE LISTED IN THE ORDER YOU WANT THEM TO APPEAR
SELECT *
FROM #KeyPairs p
PIVOT
( MAX(ValueField)
FOR KeyField IN
( [TimeStamp], [Checksum], [TicketType], [InitialUserType],
[InitialUserID], [CommunicationType], [Date], [Time],
[Service], [Duration], [Cost]
)
) AS PivotTable;
EDIT (4 YEARS LATER)
A recent upvote brought this to my attention and the I hate myself a little bit for ever posting this answer in its current form.
A much better split function would be:
CREATE FUNCTION dbo.Split
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
( WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1), (1)) n (N)),
N2(N) AS (SELECT 1 FROM N1 a CROSS JOIN N1 b),
N3(N) AS (SELECT 1 FROM N2 a CROSS JOIN N2 b),
N4(N) AS (SELECT 1 FROM N3 a CROSS JOIN N3 b),
cteTally(N) AS
( SELECT 0 UNION ALL
SELECT TOP (DATALENGTH(ISNULL(#List,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM n4
),
cteStart(N1) AS
( SELECT t.N+1
FROM cteTally t
WHERE (SUBSTRING(#List,t.N,1) = #Delimiter OR t.N = 0)
)
SELECT Item = SUBSTRING(#List, s.N1, ISNULL(NULLIF(CHARINDEX(#Delimiter,#List,s.N1),0)-s.N1,8000)),
Position = s.N1,
ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1)
FROM cteStart s
);
Then there is no need for looping at all, you just have a proper set based solution by calling the split function twice to get your EAV style data set:
DECLARE #WorkList TABLE (ID INT IDENTITY(1, 1) NOT NULL, TextField VARCHAR(MAX))
INSERT #WorkList
SELECT '08:34:52 Checksum=180957248,TicketType=6,InitialUserType=G,InitialUserID=520,CommunicationType=Incoming,Date=26-03-2012,Time=08:35:00,Service=ST,Duration=00:00:14,Cost=0.12'
UNION
SELECT '08:34:52 Checksum=180957249,TicketType=5,InitialUserType=H,InitialUserID=521,CommunicationType=Outgoing,Date=27-03-2012,Time=14:27:00,Service=ST,Duration=00:15:12,Cost=0.37';
WITH KeyPairs AS
( SELECT w.ID,
[Timestamp] = LEFT(w.TextField, CHARINDEX(' ', w.TextField)),
KeyField = MAX(CASE WHEN v.ItemNumber = 1 THEN v.Item END),
ValueField = MAX(CASE WHEN v.ItemNumber = 2 THEN v.Item END)
FROM #WorkList AS w
CROSS APPLY dbo.Split(SUBSTRING(TextField, CHARINDEX(' ', TextField) + 1, LEN(TextField)), ',') AS kp
CROSS APPLY dbo.Split(kp.Item, '=') AS v
GROUP BY w.ID, kp.ItemNumber,w.TextField
)
SELECT *
FROM KeyPairs AS kp
PIVOT
( MAX(ValueField)
FOR KeyField IN
( [Checksum], [TicketType], [InitialUserType],
[InitialUserID], [CommunicationType], [Date], [Time],
[Service], [Duration], [Cost]
)
) AS pvt;