I am using MS SQL 2005
I have a problem, which currently I am battling to get a solution.
I have a Table, with a these columns : NameList;Time
The Namelist Column has comma delimited data in it. The table data is as such:
Namelist Time
John Smith, Jeremy Boyle, Robert Brits, George Aldrich 5
John Smith, Peter Hanson 15
Jeremy Boyle, Robert Brits 10
....
I need some sort of SQL expression that will provide me with this end result:
Name Total_Time
John Smith 20
Jeremy Boyle 15
Robert Brits 15
Etc......
Basically the expression must find All the names in the rows and math those names with the names in the other rows and add the times together for each user.
The Idea I have is to convert the comma delimited data into rows and count the distinct records of each then somehow know what the time for it is... then multiply..... but I have no idea on how to implement it
Any Help would be much appreciated
Thanks,
I prefer the number table approach to split a string in TSQL
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
ListValue
-----------------------
1
2
3
4
5
6777
(6 row(s) affected)
Your can now use a CROSS APPLY to split every row in your table like:
DECLARE #YourTable table (NameList varchar(5000), TimeOf int)
INSERT INTO #YourTable VALUES ('John Smith, Jeremy Boyle, Robert Brits, George Aldrich', 5)
INSERT INTO #YourTable VALUES ('John Smith, Peter Hanson', 15)
INSERT INTO #YourTable VALUES ('Jeremy Boyle, Robert Brits', 10)
SELECT
st.ListValue AS NameOf, SUM(o.TimeOf) AS TimeOf
FROM #YourTable o
CROSS APPLY dbo.FN_ListToTable(',',o.NameList) AS st
GROUP BY st.ListValue
ORDER BY st.ListValue
OUTPUT:
NameOf TimeOf
----------------------- -----------
George Aldrich 5
Jeremy Boyle 15
John Smith 20
Peter Hanson 15
Robert Brits 15
(5 row(s) affected)
Using this, I would recommend that you alter your table design and use this output to INSERT into a new table. That would be a more normalized approach. Also Don't use reserved words for column names, it makes it a hassle. Notice how I use "NameOf" and "TimeOf", so I avoid using reserved words.
Either: Search for other answers to fix your data on the fly, slowly, and repeatedly
Or: Normalise. Why do you think normalisation exists and why people bang on about it?
You could create a table-valued function to split the namelist into many rows:
if object_id('dbo.fnSplitNamelist') is not null
drop function dbo.fnSplitNamelist
go
create function dbo.fnSplitNamelist(
#namelist varchar(max))
returns #names table (
name varchar(50))
as
begin
declare #start int
declare #end int
set #start = 0
while IsNull(#end,0) <> len(#namelist) + 1
begin
set #end = charindex(',', #namelist, #start)
if #end = 0
set #end = len(#namelist) + 1
insert into #names select ltrim(rtrim(
substring(#namelist,#start,#end-#start)))
set #start = #end + 1
end
return
end
go
You can use a cross apply to return the names for each namelist. Then you can use group by to sum the time per user:
declare #YourTable table (namelist varchar(1000), time int)
insert into #YourTable
select 'John Smith, Jeremy Boyle, Robert Brits, George Aldrich', 5
union all select 'John Smith, Peter Hanson', 15
union all select 'Jeremy Boyle, Robert Brits', 10
select fn.name, sum(t.time)
from #YourTable t
cross apply fnSplitNamelist(t.namelist) fn
group by fn.name
This results in:
George Aldrich 5
Jeremy Boyle 15
John Smith 20
Peter Hanson 15
Robert Brits 15
The best option is to normalise the data. Then it would be a lot easier to work with.
The second best option would be to use a recursive query to pick a name at a time from each name list and return as a list of separate names and their respecive time from each record, then use grouping to sum the times for each name.
No need for user defined functions or pre-created tables. ;)
with NameTime ([Name], [Time], Namelist)
as (
select cast(null as varchar(100)), [Time], Namelist
from NamelistTime
union all
select
case when Pos = 0 then NameList else substring(Namelist, 1, Pos - 1) end,
[Time],
case when Pos = 0 then null else substring(NameList, Pos + 2, len(Namelist) - Pos - 1) end
from (
select [Time], Namelist, Pos = charindex(', ', Namelist)
from NameTime
) x
where Namelist is not null
)
select [Name], sum([Time])
from NameTime
where [Name] is not null
group by [Name]
In contrast, working with normalised data, it would be as simple as:
select p.Name, sum(n.Time)
from NamelistTime n
inner join Person p on p.PersonId = n.PersonId
group by p.Name
Related
I have the below sample data and I'm trying to extract the last number within the string. I have the following, which gets me part of the way there, but I'm not sure how to get the value where it isn't the last word.
right(TextDescription, patindex('%[^0-9]%',reverse(TextDescription)) - 1)
The result should be:
ID
code
1
10015662
2
100040344
3
10015370
4
NULL
5
400337
Sample data
Create Table #TestData
(
ID int,
TextDescription varchar(100)
)
insert into #TestData Values (1,'Data From JOE BLOGGS 10015662 tree 10015662')
insert into #TestData Values (2,'Fast Data From JOHN SMITH 10004034 MARY SMITH 100040344 plant')
insert into #TestData Values (3,'Data In 10015370 pot JONES')
insert into #TestData Values (4,'Fast Data From LEE tree')
insert into #TestData Values (5,'Direct Data 106600 JANE GREEN 400337')
Just another option using a a bit of JSON which will convert the string into an array and [key] will maintain the sequence.
Select A.ID
,B.Value
From #TestData A
Outer Apply (
Select top 1 Value
From OpenJSON( '["'+replace(string_escape(TextDescription,'json'),' ','","')+'"]' )
Where try_convert(int,Value) is not null
Order by [key] Desc
) B
Results
ID Value
1 10015662
2 100040344
3 10015370
4 NULL
5 400337
The following will locate the last digit, trim the string at that point, find the preceding non-digit, and then perform another trim to get the final result. As in your original post, calculations are done on a reversed string to accommodate the lack of of a LASTPATINDEX() function. CROSS APPLY is used to build up intermediate results and to avoid duplication of subexpressions.
select T.*, P1.Pos1, P2.Pos2, N.Result
from #TestData T
cross apply (select reverse(TextDescription) AS Reversed) R
cross apply (select nullif(patindex('%[0-9]%', R.Reversed), 0) AS Pos1) P1
cross apply (select stuff(R.Reversed, 1, P1.Pos1 - 1, '') AS Trim1) T1
cross apply (select patindex('%[^0-9]%', T1.Trim1 + 'X') AS Pos2) P2
cross apply (select reverse(left(T1.Trim1, P2.Pos2 - 1)) AS Result) N
-- Partly reduced
select T.*, reverse(left(T1.Trim1, patindex('%[^0-9]%', T1.Trim1 + 'X') - 1)) AS Result
from #TestData T
cross apply (select reverse(TextDescription) AS Reversed) R
cross apply (select stuff(R.Reversed, 1, nullif(patindex('%[0-9]%', R.Reversed), 0) - 1, '') AS Trim1) T1
This will handle a variety of forms, not just space-delimited values.
See this db<>fiddle.
If your interesting values are always found at the end, and are always preceeded by a non-digit, you can use the SUBSTRING with:
lower boundary being the last non-digit before the last number location
length being the difference between last non-digit and first value of last number
WITH cte AS (
SELECT ID, TextDescription,
PATINDEX('%[0-9][^0-9]%', REVERSE(TextDescription) + ' ') AS first_space,
PATINDEX('%[0-9]%' , REVERSE(TextDescription) ) AS last_digit
FROM #TestData
)
SELECT ID,
SUBSTRING(TextDescription,
LEN(TextDescription) -first_space +1,
first_space+1 -last_digit) AS code
FROM cte
Check the demo here.
Please try the following solution leveraging XML and XQuery.
Notable points:
CROSS APPLY is tokenizing TextDescription column as XML.
XQuery FLWOR expression is checking every token if it is castable as
INTEGER data type. And filtering them out if they are not.
XPath predicate [last()] is giving us last INTEGER value.
SQL
-- DDL and sample data population, start
DECLARE #tbl Table (ID INT IDENTITY PRIMARY KEY, TextDescription varchar(100));
INSERT INTO #tbl (TextDescription) VALUES
('Data From JOE BLOGGS 10015662 tree 10015662'),
('Fast Data From JOHN SMITH 10004034 MARY SMITH 100040344 plant'),
('Data In 10015370 pot JONES'),
('Fast Data From LEE tree'),
('Direct Data 106600 JANE GREEN 400337');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = SPACE(1);
SELECT t.*
, c.query('for $x in /root/r[not(empty(xs:int(.)))]
return $x
').value('(/r[last()]/text())[1]','INT') AS [code]
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(TextDescription, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
ORDER BY t.ID;
Output
ID
TextDescription
code
1
Data From JOE BLOGGS 10015662 tree 10015662
10015662
2
Fast Data From JOHN SMITH 10004034 MARY SMITH 100040344 plant
100040344
3
Data In 10015370 pot JONES
10015370
4
Fast Data From LEE tree
NULL
5
Direct Data 106600 JANE GREEN 400337
400337
CREATE FUNCTION [ExtractInteger](#String VARCHAR(2000))
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #Count INT
DECLARE #IntNumbers VARCHAR(1000)
SET #Count = 0
SET #IntNumbers = ''
WHILE #Count <= LEN(#String)
BEGIN
IF SUBSTRING(#String,#Count,1) = ' '
BEGIN
SET #IntNumbers = #IntNumbers + ' '
END
IF SUBSTRING(#String,#Count,1) >= '0'
AND SUBSTRING(#String,#Count,1) <= '9'
BEGIN
SET #IntNumbers = #IntNumbers + SUBSTRING(#String,#Count,1)
END
SET #Count = #Count + 1
END
RETURN LTRIM(RTRIM(#IntNumbers))
END
The ExtractInteger function will fetch only numbers and spaces, and the below select will take the last word as number:
select right(dbo.ExtractInteger('My 3rd Phone Number is 323-111-CALL'), charindex(' ', reverse(dbo.ExtractInteger('My 3rd Phone Number is 323-111-CALL')) + ' ') - 1)
I have a column in a table that looks like this
Name
WALKER^JAMES^K^^
ANDERSON^MICHAEL^R^^
HUFF^CHRIS^^^
WALKER^JAMES^K^^
SWEARINGEN^TOMMY^L^^
SMITH^JOHN^JACCOB^^
I need to write a query that looks like this
Name
FirstName
LastName
MiddleName
WALKER^JAMES^K^^
JAMES
WALKER
K
ANDERSON^MICHAEL^R^^
MICHAEL
ANDERSON
R
HUFF^CHRIS^^^
CHRIS
HUFF
BUTLER^STEWART^M^^
STEWART
BUTLER
M
SWEARINGEN^TOMMY^L^^
TOMMY
SWEARINGEN
L
SMITH^JOHN^JACCOB^^
JOHN
SMITH
JACCOB
I need help generating the LastName column.
This is what I've tried so far
SUBSTRING
(
--SEARCH THE NAME COLUMN
Name,
--Starting after the first '^'
CHARINDEX('^', Name) + 1 ),
--Index of second ^ minus the index of the first ^
(CHARINDEX('^', PatientName, CHARINDEX('^', PatientName) +1)) - (CHARINDEX('^', PatientName))
)
This produces:
Invalid length parameter passed to the LEFT or SUBSTRING function.
I know this can work because if I change the minus sign to a plus sign it performs as expected.
It produces the right integer.
Where am I going wrong? Is there a better way to do this?
If you are using the latest SQL Server versions 2016 13.x or higher, you can maximize the use of string_split function with ordinal (position).
declare #strTable table(sqlstring varchar(max))
insert into #strTable (sqlstring) values ('WALKER^JAMES^K^^')
insert into #strTable (sqlstring) values ('ANDERSON^MICHAEL^R^^')
insert into #strTable (sqlstring) values ('HUFF^CHRIS^^^')
insert into #strTable (sqlstring) values ('SWEARINGEN^TOMMY^L^^');
with tmp as
(select value s, Row_Number() over (order by (select 0)) n from #strTable
cross apply String_Split(sqlstring, '^', 1))
select t2.s as FirstName, t1.s as LastName, t3.s as MiddleInitial from tmp t1
left join tmp t2 on t2.n-t1.n = 1
left join tmp t3 on t3.n-t1.n = 2
where t1.n = 1 or t1.n % 5 = 1
I recommend SUBSTRING() as it will perform the best. The challenge with SUBSTRING is it's hard to account to keep track of the nested CHARDINDEX() calls so it's better to break the calculation into pieces. I use CROSS APPLY to alias each "^" found and start from there to search for the next. Also allows to do NULLIF() = 0, so if it can't find the "^", it just returns a NULL instead of erroring out
Parse Delimited String using SUBSTRING() and CROSS APPLY
DROP TABLE IF EXISTS #Name
CREATE TABLE #Name (ID INT IDENTITY(1,1) PRIMARY KEY,[Name] varchar(255))
INSERT INTO #Name
VALUES ('WALKER^JAMES^K^^')
,('ANDERSON^MICHAEL^R^^')
,('HUFF^CHRIS^^^')
,('SWEARINGEN^TOMMY^L^^');
SELECT ID
,A.[Name]
,LastName = NULLIF(SUBSTRING(A.[Name],0,idx1),'')
,FirstName = NULLIF(SUBSTRING(A.[Name],idx1+1,idx2-idx1-1),'')
,MiddleInitial = NULLIF(SUBSTRING(A.[Name],idx2+1,idx3-idx2-1),'')
FROM #Name AS A
CROSS APPLY (SELECT idx1 = NULLIF(CHARINDEX('^',[Name]),0)) AS B
CROSS APPLY (SELECT idx2 = NULLIF(CHARINDEX('^',[Name],idx1+1),0)) AS C
CROSS APPLY (SELECT idx3 = NULLIF(CHARINDEX('^',[Name],idx2+1),0)) AS D
Ok so I have a table called PEOPLE that has a name column. In the name column is a name, but its totally a mess. For some reason its not listed such as last, first middle. It's sitting like last,first,middle and last first (and middle if there) are separated by a comma.. two commas if the person has a middle name.
example:
smith,steve
smith,steve,j
smith,ryan,tom
I'd like the second comma taken away (for parsing reason ) spaces put after existing first comma so the above would come out looking like:
smith, steve
smith, steve j
smith, ryan tom
Ultimately I'd like to be able to parse the names into first, middle, and last name fields, but that's for another post :_0. I appreciate any help.
thank you.
Drop table T1;
Create table T1(Name varchar(100));
Insert T1 Values
('smith,steve'),
('smith,steve,j'),
('smith,ryan,tom');
UPDATE T1
SET Name=
CASE CHARINDEX(',',name, CHARINDEX(',',name)+1) WHEN
0 THEN Name
ELSE
LEFT(name,CHARINDEX(',',name, CHARINDEX(',',name)+1)-1)+' ' +
RIGHT(name,LEN(Name)-CHARINDEX(',',name, CHARINDEX(',',name)+1))
END
Select * from T1
This seems to work. Not the most concise but avoids cursors.
DECLARE #people TABLE (name varchar(50))
INSERT INTO #people
SELECT 'smith,steve'
UNION
SELECT 'smith,steve,j'
UNION
SELECT 'smith,ryan,tom'
UNION
SELECT 'commaless'
SELECT name,
CASE
WHEN CHARINDEX(',',name) > 0 THEN
CASE
WHEN CHARINDEX(',',name,CHARINDEX(',',name) + 1) > 0 THEN
STUFF(STUFF(name, CHARINDEX(',',name,CHARINDEX(',',name) + 1), 1, ' '),CHARINDEX(',',name),1,', ')
ELSE
STUFF(name,CHARINDEX(',',name),1,', ')
END
ELSE name
END AS name2
FROM #people
Using a table function to split apart the names with a delimiter and for XML Path to stitch them back together, we can get what you're looking for! Hope this helps!
Declare #People table(FullName varchar(200))
Insert Into #People Values ('smith,steve')
Insert Into #People Values ('smith,steve,j')
Insert Into #People Values ('smith,ryan,tom')
Insert Into #People Values ('smith,john,joseph Jr')
Select p.*,stuff(fn.FullName,1,2,'') as ModifiedFullName
From #People p
Cross Apply (
select
Case When np.posID<=2 Then ', ' Else ' ' End+np.Val
From #People n
Cross Apply Custom.SplitValues(n.FullName,',') np
Where n.FullName=p.FullName
For XML Path('')
) fn(FullName)
Output:
ModifiedFullName
smith, steve
smith, steve j
smith, ryan tom
smith, john joseph Jr
SplitValues table function definition:
/*
This Function takes a delimited list of values and returns a table containing
each individual value and its position.
*/
CREATE FUNCTION [Custom].[SplitValues]
(
#List varchar(max)
, #Delimiter varchar(1)
)
RETURNS
#ValuesTable table
(
posID int
,val varchar(1000)
)
AS
BEGIN
WITH Cte AS
(
SELECT CAST('<v>' + REPLACE(#List, #Delimiter, '</v><v>') + '</v>' AS XML) AS val
)
INSERT #ValuesTable (posID,val)
SELECT row_number() over(Order By x) as posID, RTRIM(LTRIM(Split.x.value('.', 'VARCHAR(1000)'))) AS val
FROM Cte
CROSS APPLY val.nodes('/v') Split(x)
RETURN
END
GO
String manipulation in SQLServer, outside of writing your own User Defined Function, is limited but you can use the PARSENAME function for your purposes here. It takes a string, splits it on the period character, and returns the segment you specify.
Try this:
DECLARE #name VARCHAR(100) = 'smith,ryan,tom'
SELECT REVERSE(PARSENAME(REPLACE(REVERSE(#name), ',', '.'), 1)) + ', ' +
REVERSE(PARSENAME(REPLACE(REVERSE(#name), ',', '.'), 2)) +
COALESCE(' ' + REVERSE(PARSENAME(REPLACE(REVERSE(#name), ',', '.'), 3)), '')
Result: smith, ryan tom
If you set #name to 'smith,steve' instead, you'll get:
Result: smith, steve
Segment 1 actually gives you the last segment, segment 2 the second to last etc. Hence I've used REVERSE to get the order you want. In the case of 'steve,smith', segment 3 will be null, hence the COALESCE to add an empty string if that is the case. The REPLACE of course changes the commas to periods so that the split will work.
Note that this is a bit of a hack. PARSENAME will not work if there are more than four parts and this will fail if the name happens to contain a period. However if your data conforms to these limitations, hopefully it provides you with a solution.
Caveat: it sounds like your data may be inconsistently formatted. In that case, applying any automated treatment to it is going to be risky. However, you could try:
UPDATE people SET name = REPLACE(name, ',', ' ')
UPDATE people SET name = LEFT(name, CHARINDEX(' ', name)-1)+ ', '
+ RIGHT(name, LEN(name) - CHARINDEX(' ', name)
That'll work for the three examples you give. What it will do to the rest of your set is another question.
Here's an example with CHARINDEX() and SUBSTRING
WITH yourTable
AS
(
SELECT names
FROM
(
VALUES ('smith,steve'),('smith,steve,j'),('smith,ryan,tom')
) A(names)
)
SELECT names AS old,
CASE
WHEN comma > 0
THEN SUBSTRING(spaced_names,0,comma + 1) --before the comma
+ SUBSTRING(spaced_names,comma + 2,1000) --after the comma
ELSE spaced_names
END AS new
FROM yourTable
CROSS APPLY(SELECT CHARINDEX(',',names,CHARINDEX(',',names) + 1),REPLACE(names,',',', ')) AS CA(comma,spaced_names)
I have data like below - one column with a value, the second with a count:
Name Count
----- -----
John 2
Smith 3
I want an output like below - each row consisting of the value in the first column repeated n times, where n is the value of the second column:
John,John
Smith,Smith,Smith
Is there a built-in Oracle function or SQL query that could be used to achieve this? If PL/SQL is required, that would also be helpful.
Looks to me that RPAD can do it
This is NOT TESTED as I don't have an Oracle db to hand
RPAD("", (LENGTH(name)+1)*count -1, name||',')
(rpad a blank string with count copies of the string, by requiring the result to be (length(name)+1)*count -1 long. The -1 is to remove the trailing comma )
Hat tip to OracleUser for the appending-the comma bit.
I'm not familiar with oracle, but if you know how to use recursion in oracle then you might be able to convert my T-SQL code to what works for you...
Step 1. Create test table containing your sample data:
CREATE TABLE #t ([Name] VARCHAR(20), [Count] INT);
INSERT INTO #t VALUES('John',2)
INSERT INTO #t VALUES('Smith',3)
SELECT * FROM #t
This gives the following output:
Name Count
-------------------- -----------
John 2
Smith 3
Step 2. Recursive Query to get desired output:
DECLARE #Separator VARCHAR(3); SET #Separator = ','
;WITH myCTE AS (
SELECT 1 AS [Count], [Name], [Count] AS RequiredCount, [Name] AS Result FROM #t
UNION ALL
SELECT [Count] + 1, [Name], RequiredCount, CAST(Result + #Separator + [Name] AS VARCHAR(20)) FROM myCTE WHERE RequiredCount > [Count]
)
SELECT [Count], [Name], Result FROM myCTE WHERE [Count] = RequiredCount
This gives the following output:
Count Name Result
----------- -------------------- --------------------
3 Smith Smith,Smith,Smith
2 John John,John
If this doesn't make sense to you, post a comment... I'll try and explain. I'm sure someone familiar with PL/SQL can help you convert this to meet your needs.
All the best!
I am in middle of upgrading from a poorly designed legacy database to a new database. In the old database there is tableA with fields Id and Commodities. Id is the primary key and contains an int and Commodities contains a comma delimited list.
TableA:
id | commodities
1135 | fish,eggs,meat
1127 | flour,oil
In the new database, I want tableB to be in the form id, commodity where each commodity is a single item from the comma delimited list in tableA.
TableB:
id | commodity
1135 | fish
1135 | eggs
1135 | meat
1127 | flour
1127 | oil
I have a function, functionA, that when given an id, a list, and a delimiter, returns a table with an id and item field. How can I use this function to turn the two fields from tableA into tableB?
(Note: I had trouble figuring out what to title this question. Please feel free to edit the title to make it more accurately reflect the question!)
Here is the function code:
ALTER FUNCTION dbo.functionA
(
#id int,
#List VARCHAR(6000),
#Delim varchar(5)
)
RETURNS
#ParsedList TABLE
(
id int,
item VARCHAR(6000)
)
AS
BEGIN
DECLARE #item VARCHAR(6000), #Pos INT
SET #List = LTRIM(RTRIM(#List))+ #Delim
SET #Pos = CHARINDEX(#Delim, #List, 1)
WHILE #Pos > 0
BEGIN
SET #item = LTRIM(RTRIM(LEFT(#List, #Pos - 1)))
IF #item <> ''
BEGIN
INSERT INTO #ParsedList (id, item)
VALUES (#id, CAST(#item AS VARCHAR(6000)))
END
SET #List = RIGHT(#List, LEN(#List) - #Pos)
SET #Pos = CHARINDEX(#Delim, #List, 1)
END
RETURN
END
Here's the link I posted as a comment:
http://www.sqlteam.com/article/parsing-csv-values-into-multiple-rows
You need a way to split and process the string in TSQL, there are many ways to do this. This article covers the PROs and CONs of just about every method:
Arrays and Lists in SQL Server 2000 and Earlier
You need to create a split function. This is how a split function can be used:
SELECT
*
FROM YourTable y
INNER JOIN dbo.yourSplitFunction(#Parameter) s ON y.ID=s.Value
[I prefer the number table approach to split a string in TSQL](Arrays and Lists in SQL Server 2000 and Earlier) but there are numerous ways to split strings in SQL Server, see the previous link, which explains the PROs and CONs of each.
For the Numbers Table method to work, you need to do this one time table setup, which will create a table Numbers that contains rows from 1 to 10,000:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION inline_split_me (#SplitOn char(1),#param varchar(7998)) RETURNS TABLE AS
RETURN(SELECT substring(#SplitOn + #param + ',', Number + 1,
charindex(#SplitOn, #SplitOn + #param + #SplitOn, Number + 1) - Number - 1)
AS Value
FROM Numbers
WHERE Number <= len(#SplitOn + #param + #SplitOn) - 1
AND substring(#SplitOn + #param + #SplitOn, Number, 1) = #SplitOn)
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.inline_split_me(';','1;22;333;4444;;') where LEN(Value)>0
OUTPUT:
Value
----------------------
1
22
333
4444
(4 row(s) affected)
to make you new table use this:
--set up tables:
create table TableA (id int, commodities varchar(8000))
INSERT TableA VALUES (1135,'fish,eggs,meat')
INSERT TableA VALUES (1127,'flour,oil')
Create table TableB (id int, commodities varchar(8000))
--populate TableB
INSERT TableB
(id, commodities)
SELECT
a.id,c.value
FROM TableA a
CROSS APPLY dbo.inline_split_me(',',a.commodities) c
--show tableB contents:
select * from TableB
OUTPUT:
id commodities
----------- -------------
1135 fish
1135 eggs
1135 meat
1127 flour
1127 oil
(5 row(s) affected)
EDIT after Conrad Frix comment about SQL Server 2000 not supporting CROSS APPLY
this will do the same:
INSERT TableB
(id, commodities)
SELECT
a.id,NullIf(SubString(',' + a.commodities + ',' , number , CharIndex(',' , ',' + a.commodities + ',' , number) - number) , '')
FROM TableA a
INNER JOIN Numbers n ON 1=1
WHERE SubString(',' + a.commodities + ',' , number - 1, 1) = ','
AND CharIndex(',' , ',' + a.commodities + ',' , number) - number > 0
AND number <= Len(',' + a.commodities + ',')
and is based on the code from the link in the answer by #Rup. It basically removes the function call and does the split in the main query (using a similar Numbers table split), so no need for a CROSS APPLY.
You write a SQL batch that loops through table A and inserts into table b the results of your function call.
Call me lazy, but I'd pull the combined rows out of the database, split them, then reinsert the split rows. This kind of thing seems kind of unnatural for SQL...
SSIS has a pretty handy Unpivot transform if that's available to you.
create table Project (ProjectId int, Description varchar(50));
insert into Project values (1, 'Chase tail, change directions');
insert into Project values (2, 'ping-pong ball in clothes dryer');
create table ProjectResource (ProjectId int, ResourceId int, Name varchar(15));
insert into ProjectResource values (1, 1, 'Adam');
insert into ProjectResource values (1, 2, 'Kerry');
insert into ProjectResource values (1, 3, 'Tom');
insert into ProjectResource values (2, 4, 'David');
insert into ProjectResource values (2, 5, 'Jeff');
-- a bit of SQL magic involving XML and voila
SELECT *,
(SELECT Name + ' ' AS [text()]
FROM ProjectResource pr
WHERE pr.ProjectId = p.ProjectId
FOR XML PATH ('')) AS ResourceList
FROM Project p
The output of this will be :
ProjectId Description ResourceList
1 Chase tail, change directions Adam Kerry Tom
2 ping-pong ball in clothes dryer David Jeff