Dealing with splitting strings in SQL - sql

Currently I'm working on a project where we just added the ability to have multiple values stored within a field. Previously, it stored a single value, but now it contains multiple. Below is an example of what I'm talking about.
Ex. A person's name is passed in (John Smith). Now the user can pass in multiple people's names, delimited by a ';' (John Smith;John Doe;Jane Tandy).
My issue is that we have a data source for a drop down that currently feeds off this field. Below is the SQL for it.
Select Distinct Content
From LIB_DocumentAttribute A
inner join LIB_PublicDocument B on A.PublicDocumentId = B.PublicDocumentId
Where AttributeId = (Select AttributeId From LIB_Attribute Where FieldName='Author')
and B.Status = 'Approved'
This somewhat works now. Content is the field that contains the multiple names. Now when the drop down is loaded, it pulls back the concatenated string of names (the longer one from above). I want to break it apart for the data source. So far, my only ideas is to split out the data based on the ';'. However, I need to take that split out data and apply it to the table that returns the rest of the data. Below is where I have gotten to but have become stuck on.
CREATE TABLE #Authors
(
Content varchar(MAX)
)
CREATE TABLE #Temp1
(
Content varchar(MAX)
)
CREATE TABLE #Temp2
(
Content varchar(MAX)
)
CREATE TABLE #Temp3
(
Content varchar(MAX)
)
--Load Authors table to store all Authors
INSERT INTO #Authors
Select Distinct Content
From LIB_DocumentAttribute A
inner join LIB_PublicDocument B on A.PublicDocumentId = B.PublicDocumentId
Where AttributeId = (Select AttributeId From LIB_Attribute Where FieldName='Author')
and B.Status = 'Approved'
--Take multiple Authors separated by '; ' and add to Temp1
INSERT INTO #Temp1
SELECT REPLACE(Content, '; ', ';') FROM #Authors WHERE Content LIKE '%; %'
--Remove multiple Authors separated by '; ' from Authors table
DELETE FROM #Authors
WHERE Content LIKE '%; %'
--Take multiple Authors separated by ';' and add to Temp2
INSERT INTO #Temp2
SELECT Content FROM #Authors WHERE Content LIKE '%;%'
--Remove multiple Authors separated by ';' from Authors table
DELETE FROM #Authors
WHERE Content LIKE '%;%'
--Somewhow split data and merge back together
DROP TABLE #Authors
DROP TABLE #Temp1
DROP TABLE #Temp2
DROP TABLE #Temp3
Edit:
So in the end, I came up with a solution that utilized some of the pieces that Kumar suggested. I created a function for splitting the string as he suggested and added some personal changes to make it work. Mind you this is in a table return function, with the table called #Authors, and it has one column called Content.
BEGIN
DECLARE #Temp TABLE
(
Content varchar(MAX)
)
--Load Authors table to store all Authors
INSERT INTO #Authors
Select Distinct Content
From LIB_DocumentAttribute A
inner join LIB_PublicDocument B on A.PublicDocumentId = B.PublicDocumentId
Where AttributeId = (Select AttributeId From LIB_Attribute Where FieldName='Author')
--Take multiple Authors separated by ', ' and add to Temp
INSERT INTO #Temp
SELECT REPLACE(Content, ', ', ',')
FROM #Authors;
--Remove multiple Authors separated by ', ' from Authors table
DELETE FROM #Authors
WHERE Content LIKE '%,%';
--Readd multiple Authors now separated into Authors table
INSERT INTO #Authors
SELECT s.Content
FROM #Temp
OUTER APPLY SplitString(Content,',') AS s
WHERE s.Content <> (SELECT TOP 1 a.Content FROM #Authors a WHERE s.Content = a.Content)
RETURN
END

Check the demo in fiddler link http://sqlfiddle.com/#!3/390f8/11
Create table test(name varchar(1000));
Insert into test values('AAA BBB; CCC DDD; eee fff');
CREATE FUNCTION SplitString
(
#Input NVARCHAR(MAX),
#Character CHAR(1)
)
RETURNS #Output TABLE (
Item NVARCHAR(1000)
)
AS
BEGIN
DECLARE #StartIndex INT, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output(Item)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
Declare #name varchar(100)
Declare #table as table(name varchar(1000))
Declare cur cursor for
Select name from test
Open cur
fetch next from cur into #name
while (##FETCH_STATUS = 0)
begin
Insert into #table
Select * from dbo.splitstring(#name,';')
fetch next from cur into #name
end
close cur
deallocate cur
Select * from #table

this might work.
drop table authors
GO
create table Authors (Author_ID int identity (1,1),name varchar (255), category varchar(255))
GO
insert into authors
(name,category)
select
'jane doe','nonfiction'
union
select
'Jules Verne; Mark Twain; O. Henry', 'fiction'
union
select
'John Smith; John Doe', 'nonfiction'
GO
DECLARE #table TABLE (
names VARCHAR(255)
,id INT
)
DECLARE #category VARCHAR(255)
SET #category = 'nonfiction'
DECLARE #Author_ID INT
DECLARE AuthorLookup CURSOR
FOR
SELECT Author_ID
FROM authors
WHERE category = #category
OPEN AuthorLookup
FETCH NEXT
FROM AuthorLookup
INTO #Author_ID
WHILE ##FETCH_STATUS = 0
BEGIN
IF (
SELECT CHARINDEX(';', NAME, 0)
FROM authors
WHERE Author_ID = #Author_ID
) = 0
BEGIN
INSERT INTO #table
SELECT NAME
,Author_ID
FROM authors
WHERE Author_ID = #Author_ID
END
ELSE
BEGIN
DECLARE #value VARCHAR(255)
SELECT #value = NAME
FROM authors
WHERE Author_ID = #Author_ID
WHILE len(#value) > 0
BEGIN
INSERT INTO #table
SELECT substring(#value, 0, CHARINDEX(';', #value, 0))
,#Author_ID
SELECT #value = replace(#value, substring(#value, 0, CHARINDEX(';', #value, 0) + 2), '')
IF CHARINDEX(';', #value, 0) = 0
BEGIN
INSERT INTO #table
SELECT #value
,#Author_ID
SET #value = ''
END
END
END
FETCH NEXT
FROM AuthorLookup
INTO #Author_ID
END
CLOSE AuthorLookup
DEALLOCATE AuthorLookup
SELECT *
FROM #table

You can create function to split your data
create function SplitString
(
#data nvarchar(max),
#sep char(1)
)
returns #result table (data nvarchar(max))
as
begin
declare #i int
while 1 = 1
begin
select #i = charindex(#sep, #data)
if #i = 0
begin
insert into #result
select #data
break
end
insert into #result
select rtrim(left(#data, #i - 1))
select #data = ltrim(right(#data, len(#data) - #i))
end
return
end
and use it like this:
select s.data
from test as t
outer apply SplitString(t.data,';') as s
If you're sure that you don't have special characters inside your data, you can also consider trick with xml:
;with cte as (
select
cast('<s>' + replace(data, ';', '</s><s>') + '</s>' as xml) as data
from test
)
select
t.c.value('.', 'nvarchar(max)') as data
from cte
outer apply data.nodes('s') as t(c)
sql fiddle demo

Related

splitting input string in sql and getting its value from another table

I have a table hobby and another table hobbyValue.
Hobby Table
===========
hobbies (It is a list of hobbies which user has inserted using checkbox. It gets stores hobby as Ids with a delimiter ^ for e.g hobbyId1^hobbyId2^hobbyId3^)
Hobby Value Table (it has two columns id, Value)
============
hobbyId1 | Football
hobbyId2 | baseball
hobbyId3 | chess
I am restricted to use this kind of table format because it is part of a big application. I am not sure how can I write a sql function where input string is hobbies from Hobby table and out put will be its values.
for e.g.
============================
string inputHobbies = hobbies from Hobby table , hobbyId1^hobbyId2^hobbyId3^
outputValues = input my_sql_function(inputHobbies)
outputValues should be football,baseball,chess
I dont even know how should I start for to get this.
Firstly, you will need some string split capability. If you are on Sql Server 2016+, there should be built in STRING_SPLIT function (https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql).
If not, you can use one below, which I've got some time ago from another SO question.
CREATE FUNCTION fnc_splitstring ( #stringToSplit VARCHAR(MAX) )
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX('^', #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX('^', #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
Then you can split your hobby ids and join them onto the Hobby table, get the actual name and STUFF the results into a single result row again:
CREATE FUNCTION fnc_getHobbies (#listOfHobbyIds NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #hobbies NVARCHAR(MAX)
SELECT #hobbies = STUFF(( SELECT ',' + hobby
FROM dbo.fnc_splitstring(#listOfHobbyIds) hb
INNER JOIN hobby h on h.id = hb.name
FOR XML PATH('')
), 1, 1, '')
RETURN #hobbies
END
End usage like below:
SELECT dbo.fnc_getHobbies(hobbyids)
FROM hobbies
Lastly, here is the SQL Fiddle for this: http://sqlfiddle.com/#!18/44cd9
CREATE FUNCTION [dbo].[SplitForDelimiter] -- a table-valued function
(
#delimiter VARCHAR(10),
#input VARCHAR(1000)
)
RETURNS #tempTable TABLE(
data VARCHAR(100)
)
BEGIN
DECLARE #tempstr VARCHAR(1000)
SET #tempstr = #input
WHILE(charindex(#delimiter,#tempstr,0) > 0)
BEGIN
DECLARE #t VARCHAR(100)
SET #t = Substring(#tempstr,0,(charindex(#delimiter,#tempstr,0)))
INSERT into #tempTable (data) VALUES (#t)
SET #tempstr = Substring(#tempstr,charindex(#delimiter,#tempstr,0)+1,Len(#tempstr))
if(charindex(#delimiter,#tempstr,0) <=0)
BEGIN
INSERT into #tempTable (data) VALUES (#tempstr)
END
END
Then
SELECT Value from HobbyValue HV
WHERE ID IN (select * from SplitForDelimiter('^','hobbyId1^hobbyId2^hobbyId3^'))

insert multiple splited string column to separate rows into a sql table

I have a table as shown below
Is it possible to insert the above table data into a table in separate rows?
I tried using split function on each column and stored each column result on a temp table. I have no clue how to insert into new table combining all these rows and columns as per the id. Any help or suggestion would help.
Try this answer. Hope this helps you.
DECLARE #Table TABLE(ID INT, NAME VARCHAR(10),TITLE VARCHAR(10))
INSERT INTO #Table VALUES (1,';a;b;c',';12;13;14')
DECLARE #ID INT=1
SELECT #ID ID,Items,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN1 INTO #T1 FROM dbo.split((SELECT NAME FROM #Table WHERE id=#ID),';')
SELECT #ID ID,Items,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN2 INTO #T2 FROM dbo.split((SELECT TITLE FROM #Table WHERE id=#ID),';')
SELECT T1.ID,T1.Items NAME,T2.Items TITLE
FROM #T1 T1 INNER JOIN #T2 T2 ON T1.RN1=T2.RN2
DROP TABLE #T1
DROP TABLE #T2
If you want all the values, you just try the looping method like WHILE.
DECLARE #Table TABLE(ID INT, NAME VARCHAR(10),TITLE VARCHAR(10))
INSERT INTO #Table VALUES (1,';a;b;c',';12;13;14'),(2,';c;f;u',';67;56;34'),(3,';l;k;m',';90;70;60')
DECLARE #MinID INT,#MaxID INT
SELECT #MinID=MIN(ID),#MaxID=MAX(ID) FROM #Table
CREATE TABLE #T1(ID INT,Items VARCHAR(10),RN1 INT)
CREATE TABLE #T2(ID INT,Items VARCHAR(10),RN2 INT)
WHILE #MinID<=#MaxID
BEGIN
INSERT INTO #T1
SELECT #MinID ID,Items,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN1
FROM dbo.split((SELECT NAME FROM #Table WHERE id=#MinID),';')
INSERT INTO #T2
SELECT #MinID ID,Items,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RN2
FROM dbo.split((SELECT TITLE FROM #Table WHERE id=#MinID),';')
SET #MinID=#MinID+1
END
SELECT T1.ID,T1.Items NAME,T2.Items TITLE
FROM #T1 T1 INNER JOIN #T2 T2 ON T1.ID=T2.ID AND T1.RN1=T2.RN2
DROP TABLE #T1
DROP TABLE #T2
This will produce the result, what you exactly want:
ID NAME TITLE
----------- ---------- ----------
1 a 12
1 b 13
1 c 14
2 c 67
2 f 56
2 u 34
3 l 90
3 k 70
3 m 60
Here is the split function, I used to split the Strings:
CREATE FUNCTION [dbo].[Split]
(#String VARCHAR (max), #Delimiter CHAR (1))
RETURNS
#temptable TABLE (
[items] VARCHAR (max) COLLATE SQL_Latin1_General_CP1_CI_AS NULL)
AS
begin
declare #idx int
declare #slice varchar(max)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end
Here is another method of CTE with help of XML node
There will no need to create any function.
WITH cte AS (
SELECT ID,
split.a.value('.', 'NVARCHAR(MAX)') [name],
ROW_NUMBER() OVER(ORDER BY ( SELECT 1)) RN
FROM
(
SELECT ID,
CAST('<A>'+REPLACE(name, ';', '</A><A>')+'</A>' AS XML) AS [name]
FROM <table_name>
) a
CROSS APPLY name.nodes('/A') AS split(a)),
CTE1 AS (
SELECT ID,
split.a.value('.', 'NVARCHAR(MAX)') [title],
ROW_NUMBER() OVER(ORDER BY ( SELECT 1 )) RN
FROM
(
SELECT ID,
CAST('<A>'+REPLACE(title, ';', '</A><A>')+'</A>' AS XML) AS [title]
FROM <table_name>
) aa
CROSS APPLY title.nodes('/A') AS split(a))
SELECT C.ID, C.name, C1.title FROM CTE C
JOIN CTE1 C1 ON C1.RN = C.RN
WHERE C.name != '' AND C1.title != '';
Result :
ID name title
1 a 12
1 b 13
1 s 45
2 c 67
2 f 56
2 u 34
3 l 90
3 k 70
3 m 60
Try below way .. this will save time and memory also!
This T-SQL block has dependency on dbo.SplitString function ..
T1 is my Source table T2 is my Destination table
DECLARE #c_s AS CURSOR;
DECLARE #id INT;
DECLARE #name VARCHAR(1000);
DECLARE #value VARCHAR(1000);
SET #c_s = CURSOR FOR SELECT * FROM T1;
OPEN #c_s;
FETCH #c_s INTO #id, #name, #value
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO T2
SELECT #id
, a.Value NAME
, b.value value
FROM dbo.SplitString(#name, ';') a
INNER JOIN dbo.SplitString(#value, ';') b
ON a.OrdinalPosition = b.OrdinalPosition
FETCH NEXT FROM #c_s INTO #id, #name, #value
END
here is the dbo.SplitString
CREATE FUNCTION [dbo].[SplitString](#givenString VARCHAR(8000) , #separator VARCHAR(100))
RETURNS TABLE AS
RETURN (
WITH data([start], [end]) AS (
SELECT 0 AS [start]
, CHARINDEX(#separator, #givenString) AS [end]
UNION ALL
SELECT [end] + 1
, CHARINDEX(#separator, #givenString, [end] + 1)
FROM data
WHERE [end] > 0
)
SELECT ROW_NUMBER() OVER (
ORDER BY OrdinalPosition
) OrdinalPosition
, RTRIM(LTRIM(Value)) Value
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY [start]
) OrdinalPosition
, SUBSTRING(#givenString, [start], COALESCE(NULLIF([end], 0), len(#givenString) + 1) - [start]) Value
FROM data
) r
WHERE RTRIM(Value) <> ''
AND Value IS NOT NULL
)
You can achieve this by writing a table-valued function that will split the strings according to your requirements. Once you have created this object, then you can use the T-SQL in second code snippet to get your final required table.
The definition of this table-valued function is as given below. Just copy and paste this into SSMS and run it against your database.
Split a string function
-- =============================================
-- Author: B Vidhya
-- Create date: Nov 7, 2017
-- Description: Splits a string and returns a table
-- =============================================
CREATE FUNCTION [dbo].[SplitAString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #splitTable TABLE (
ItemNumber INT IDENTITY(1,1),
Item NVARCHAR(1000)
)
AS
BEGIN
DECLARE #startIndex INT,#endIndex INT
SET #startIndex = 1
IF SUBSTRING(#string, LEN(#string) - 1, LEN(#string)) <> #delimiter
BEGIN
SET #string = #string + #delimiter
END
WHILE CHARINDEX(#delimiter, #string) > 0
BEGIN
SET #endIndex = CHARINDEX(#delimiter, #string)
INSERT INTO #splitTable(Item)
SELECT SUBSTRING(#string, #startIndex, #endIndex - 1)
SET #string = SUBSTRING(#string, #endIndex + 1, LEN(#string))
END
RETURN
END
GO
In T-SQL below, I have called the original table StackOverflowTable1 and you can replace this table name with your actual table name. Also, I am inserting final rows into a table variable. If you wanted to insert into your custom table, then you could use perform an INSERT into your table after the END of WHILE loop.
T-SQL to get your final table
DECLARE #myTable TABLE
(Id INT,
Name VARCHAR(5000),
Title VARCHAR(5000)
);
DECLARE #lastId INT= 0, #id INT, #name VARCHAR(5000), #title VARCHAR(5000);
--for each record in table perform splitting and insertion in new table
WHILE EXISTS
(
SELECT 1
FROM StackOverFlowTable1 soft
WHERE Id > #lastId
)
BEGIN
SELECT TOP (1) #id = Id,
#name = Name,
#title = Title
FROM StackOverFlowTable1 soft
WHERE Id > #lastId
ORDER BY Id;
SET #lastId = #id;
INSERT INTO #myTable
(Id,
Name,
Title
)
SELECT #id,
ss1.Item,
ss2.Item
FROM dbo.SplitString(#name, ';') ss1
INNER JOIN dbo.SplitString(#title, ';') ss2 ON ss1.ItemNumber = ss2.ItemNumber
WHERE ss1.Item <> ''
AND ss2.Item <> '';
END;
SELECT * FROM #myTable;
I do not agree with Dinesh script because it is based on RBAR.
I have very similar Split function with also return row_number along with item.
so test my script along with other sample data.
DECLARE #Table TABLE(ID INT, NAME VARCHAR(10),TITLE VARCHAR(10))
INSERT INTO #Table VALUES (1,',a,b,c',',12,13,14')
SELECT id
,t.RowVal
,a.RowVal
FROM (
SELECT t.id
,a.RowNum
,a.RowVal
,t.TITLE
FROM #Table t
CROSS APPLY (
SELECT *
FROM dbo.FN_SPLIT_VALUE(t.NAME)
) a
) t
CROSS APPLY (
SELECT *
FROM dbo.FN_SPLIT_VALUE(t.TITLE)
WHERE t.RowNum = RowNum
) a
WHERE t.RowVal <> ''

SQL Server: Replace values in field via lookup other table

I am currently running into a challenge that probably has a easy solution, but somehow I am not able to come up with it.
I have Table A with two fields that are formatted as follows:
[ID] [Codes]
1 A;B
2 D
3 A;C
And table B formatted as follows:
[ID] [Codes]
A Apple
B Orange
C Pear
D Strawberry
What I would like to do is a Lookup / Replace in order to generate the following output
a.[ID] a.[Parsed_Codes]
1 Apple;Orange
2 Strawberry
3 Apple;Pear
In short I want to replace the codes in table A, with the values associated with those codes in table B.
Of course I could just write a long replace statement (in my case there are several 100 codes), but that seems like an extremely inefficient method.
Thanks!
Simple way is by converting the Table A [Codes] column data(csv) into separate rows.
Then join with the Table B to get the respective codes. Finally convert the rows to CSV to get result. Try this.
CREATE TABLE #tablea
([ID] INT,[Codes] VARCHAR(100))
INSERT INTO #tablea
VALUES (1,'A;B'),(2,'D' ),(3,'A;C')
CREATE TABLE #tableB
([ID] VARCHAR(100),[Codes] VARCHAR(100))
INSERT INTO #tableb
VALUES ('A','Apple'),( 'B','Orange' ),
('C','Pear'),('D','Strawberry')
SELECT a.id,
a.Codes old_code,
b.Codes Parsed_Codes
INTO #final
FROM #tableb b
JOIN (SELECT id,
codes,
Split.a.value('.', 'VARCHAR(100)') [new_Codes]
FROM (SELECT id,
[Codes],
Cast ('<M>' + Replace([Codes], ';', '</M><M>')
+ '</M>' AS XML) AS Data
FROM #tablea) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)) a
ON a.new_Codes = b.id
SELECT t1.ID,
old_code,
Stuff((SELECT '; ' + CONVERT(VARCHAR, Parsed_Codes)
FROM #final b
WHERE b.ID = t1.ID
FOR XML PATH('')), 1, 2, '')
FROM #final t1
GROUP BY t1.id,
old_code
OUTPUT
ID old_code Parsed_Codes
-- -------- ------------
1 A;B Apple; Orange
2 D Strawberry
3 A;C Apple; Pear
(Note: temp table can be avoided to avoid code confusion i used temp table)
First create this function.. It's used to split a delimited string, into a variable table. Then we would be able to use this function to determine the codes from the other table and return them as one string using the STUFF function.
CREATE FUNCTION [dbo].[fnSplitString]
(
#string NVarchar(MAX)
,#delimiter Char(1) = ','
)
RETURNS #t TABLE (string NVarchar(MAX))
AS
BEGIN
DECLARE #pos Int
DECLARE #piece Varchar(500)
IF RIGHT(RTRIM(#string), 1) <> #delimiter
SET #string = #string + #delimiter
SET #pos = PATINDEX('%' + #delimiter + '%', #string)
WHILE #pos <> 0
BEGIN
SET #piece = LEFT(#string, #pos - 1)
INSERT #t
SELECT #piece
SET #string = STUFF(#string, 1, #pos, '')
SET #pos = PATINDEX('%' + #delimiter + '%', #string)
END
RETURN
END
Then run the following query...
DECLARE #result TABLE
(
[ID] Int
,[CommaDelimitedCodes] Varchar(500)
)
DECLARE #codes TABLE
(
[ID] Varchar(500)
,[FullNames] Varchar(500)
)
INSERT INTO #result
SELECT 1
,'A;B'
INSERT INTO #result
SELECT 2
,'D'
INSERT INTO #result
SELECT 3
,'A;C'
INSERT INTO #codes
SELECT 'A'
,'Apple'
INSERT INTO #codes
SELECT 'B'
,'Orange'
INSERT INTO #codes
SELECT 'C'
,'Pear'
INSERT INTO #codes
SELECT 'D'
,'Strawberry'
SELECT *
,STUFF((
SELECT ', ' + [FullNames]
FROM #codes t
WHERE id IN (SELECT *
FROM dbo.fnSplitString(r.[CommaDelimitedCodes], ';'))
FOR
XML PATH('')
), 1, 2, '') AS taglist
FROM #result AS r
I created two variable tables to test - but obviously in your case you'd need to replace those with actual field names in your tables.

Comma-separated values (CSV) parameter filtering

Need help on how to improve my SQL script for better performance. dbo.Products table has a million rows. I'm hesitant to rewrite it using dynamic SQL. Thanks!
DECLARE
#Brand varchar(MAX) = 'Brand 1, Brand 2, Brand 3',
#ItemCategory varchar(MAX) = 'IC1, IC2, IC3, IC4, IC5'
--will return all records if params where set to #Brand = NULL, #ItemCategory = NULL
SELECT
[Brand],
SUM([Amount]) AS [Amount]
FROM dbo.Products (NOLOCK)
LEFT JOIN [dbo].[Split](#Brand, ',') FilterBrand ON Brand = [FilterBrand].[Items]
LEFT JOIN [dbo].[Split](#ItemCategory, ',') FilterItemCategory ON ItemCategory = [FilterItemCategory].[Items]
WHERE
(#Brand IS NULL OR (#Brand IS NOT NULL AND [FilterBrand].[Items] IS NOT NULL)) AND
(#ItemCategory IS NULL OR (#ItemCategory IS NOT NULL AND [FilterItemCategory].[Items] IS NOT NULL))
GROUP BY
[Brand]
Below is the split table-valued function that I found on the web:
CREATE function [dbo].[Split]
(
#String varchar(8000),
#Delimiter char(1)
)
RETURNS #Results TABLE (Items varchar(4000))
AS
BEGIN
IF (#String IS NULL OR #String = '') RETURN
DECLARE #i int, #j int
SELECT #i = 1
WHILE #i <= LEN(#String)
BEGIN
SELECT #j = CHARINDEX(#Delimiter, #String, #i)
IF #j = 0
BEGIN
SELECT #j = len(#String) + 1
END
INSERT #Results SELECT RTRIM(SUBSTRING(#String, #i, #j - #i))
SELECT #i = #j + LEN(#Delimiter)
END
RETURN
END
Following solution are with out using functions
Declare #IDs Varchar(100)
SET #IDs = '2,4,6'
Select IsNull(STUFF((Select ', '+ CAST([Name] As Varchar(100)) From [TableName]
Where CharIndex(','+Convert(Varchar,[ID])+',', ','+#IDs+',')> 0
For XML Path('')),1,1,''),'') As [ColumnName]
Here is the function I use. I also have another that wraps this to return numeric values which I find helpful as well.
Edit: Sorry, as for how to improve the performance of the query, I usually split the values into table variables and perform my joins to that but that probably won't change your performance, just your readability. The only thing I can see in terms of performance is your double checking whether your joins produce anything. You really can't get much better performance with two conditional left joins on two tables. It basically boils down to indexes at that point.
(#Brand IS NULL OR [FilterBrand].[Items] IS NOT NULL)
Function:
ALTER FUNCTION [dbo].[fn_SplitDelimittedList]
(
#DelimittedList varchar(8000),
#Delimitter varchar(20)
)
RETURNS
#List TABLE
(
Item varchar(100)
)
AS
BEGIN
DECLARE #DelimitterLength INT
SET #DelimitterLength = LEN(#Delimitter)
-- Tack on another delimitter so we get the last item properly
set #DelimittedList = #DelimittedList + #Delimitter
declare #Position int
declare #Item varchar(500)
set #Position = patindex('%' + #Delimitter + '%' , #DelimittedList)
while (#Position <> 0)
begin
set #Position = #Position - 1
set #Item = LTRIM(RTRIM(left(#DelimittedList, #Position)))
INSERT INTO #List (Item) VALUES (#Item)
set #DelimittedList = stuff(#DelimittedList, 1, #Position + #DelimitterLength, '')
set #Position = patindex('%' + #Delimitter + '%' , #DelimittedList)
end
RETURN
END
Hey just try the split function I have created without using any while loops here.And just use this in place of your split function and use col to match in LEFT join.
ALTER function dbo.SplitString(#inputStr varchar(1000),#del varchar(5))
RETURNS #table TABLE(col varchar(100))
As
BEGIN
DECLARE #t table(col1 varchar(100))
INSERT INTO #t
select #inputStr
if CHARINDEX(#del,#inputStr,1) > 0
BEGIN
;WITH CTE as(select ROW_NUMBER() over (order by (select 0)) as id,* from #t)
,CTE1 as (
select id,ltrim(rtrim(LEFT(col1,CHARINDEX(#del,col1,1)-1))) as col,RIGHT(col1,LEN(col1)-CHARINDEX(#del,col1,1)) as rem from CTE
union all
select c.id,ltrim(rtrim(LEFT(rem,CHARINDEX(#del,rem,1)-1))) as col,RIGHT(rem,LEN(rem)-CHARINDEX(#del,rem,1))
from CTE1 c
where CHARINDEX(#del,rem,1)>0
)
INSERT INTO #table
select col from CTE1
union all
select rem from CTE1 where CHARINDEX(#del,rem,1)=0
END
ELSE
BEGIN
INSERT INTO #table
select col1 from #t
END
RETURN
END
DECLARE #Brand varchar(MAX) = 'Brand 1,Brand 2,Brand 3',
#ItemCategory varchar(MAX) = ' IC1 A ,IC2 B , IC3 C, IC4 D' --'IC1, IC2, IC3, IC4, IC5'
select * from dbo.SplitString(#ItemCategory,',')

Insert multiple rows into temp table with one command in SQL2005

I've got some data in the following format:
-1,-1,-1,-1,701,-1,-1,-1,-1,-1,304,390,403,435,438,439,442,455
I need to insert it into a temp table like this:
CREATE TABLE #TEMP
(
Node int
)
So that I can use it in a comparison with data in another table.
The data above represents separate rows of the "Node" column.
Is there an easy way to insert this data, all in one command?
Also, the data will actually being coming in as seen, as a string... so I need to be able to just concat it into the SQL query string. I can obviously modify it first if needed.
Try something like
CREATE TABLE #TEMP
(
Node int
)
DECLARE #textXML XML
DECLARE #data NVARCHAR(MAX),
#delimiter NVARCHAR(5)
SELECT #data = '-1,-1,-1,-1,701,-1,-1,-1,-1,-1,304,390,403,435,438,439,442,455 ',
#delimiter = ','
SELECT #textXML = CAST('<d>' + REPLACE(#data, #delimiter, '</d><d>') + '</d>' AS XML)
INSERT INTO #TEMP
SELECT T.split.value('.', 'nvarchar(max)') AS data
FROM #textXML.nodes('/d') T(split)
SELECT * FROM #TEMP
DROP TABLE #TEMP
You can create a query dynamically like this:
declare #sql varchar(1000)
set #sql = 'insert into #TEMP select ' + replace(#values, ',', ' union all select ')
exec #sql
As always when creating queries dynamically, you have to be careful so that you only use trusted data.
I would create a function that would return a table variable and then join that function into the select
Use:
select * from myTable a
inner join dbo.buildTableFromCSV('1,2,3') on a.id = b.theData
Here is my function for doing this
CREATE FUNCTION [dbo].[buildTableFromCSV] ( #csvString varchar(8000) ) RETURNS #myTable TABLE (ID int identity (1,1), theData varchar(100))
AS BEGIN
DECLARE #startPos Int -- position to chop next block of chars from
DECLARE #currentPos Int -- position to current character we're examining
DECLARE #strLen Int
DECLARE #c char(1) -- current subString
-- variable initalization
-- -------------------------------------------------------------------------------------------------------------------------------------------------
SELECT #csvString = #csvString + ','
SELECT #startPos = 1
SELECT #currentPos = 1
SELECT #strLen = Len(#csvString)
-- loop over string and build temp table
-- -------------------------------------------------------------------------------------------------------------------------------------------------
WHILE #currentPos <= #strLen BEGIN
SET #c = SUBSTRING(#csvString, #currentPos, 1 )
IF ( #c = ',' ) BEGIN
IF ( #currentPos - #startPos > 0 ) BEGIN
INSERT
INTO #myTable ( theData )
VALUES ( CAST( SUBSTRING ( #csvString, #startPos, #currentPos - #startPos) AS varchar ) )
END
ELSE
begin
INSERT
INTO #myTable ( theData )
VALUES ( null )
end
SELECT #startPos = #currentPos + 1
END
SET #currentPos = #currentPos + 1
END
delete from #myTable where theData is null
return
END