Consider a database table holding names, with three rows:
Peter
Paul
Mary
Is there an easy way to turn this into a single string of Peter, Paul, Mary?
If you are on SQL Server 2017 or Azure, see Mathieu Renda answer.
I had a similar issue when I was trying to join two tables with one-to-many relationships. In SQL 2005 I found that XML PATH method can handle the concatenation of the rows very easily.
If there is a table called STUDENTS
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Result I expected was:
SubjectID StudentName
---------- -------------
1 Mary, John, Sam
2 Alaina, Edward
I used the following T-SQL:
SELECT Main.SubjectID,
LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
(
SELECT DISTINCT ST2.SubjectID,
(
SELECT ST1.StudentName + ',' AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH (''), TYPE
).value('text()[1]','nvarchar(max)') [Students]
FROM dbo.Students ST2
) [Main]
You can do the same thing in a more compact way if you can concat the commas at the beginning and use substring to skip the first one so you don't need to do a sub-query:
SELECT DISTINCT ST2.SubjectID,
SUBSTRING(
(
SELECT ','+ST1.StudentName AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH (''), TYPE
).value('text()[1]','nvarchar(max)'), 2, 1000) [Students]
FROM dbo.Students ST2
This answer may return unexpected results For consistent results, use one of the FOR XML PATH methods detailed in other answers.
Use COALESCE:
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(#Names + ', ', '') + Name
FROM People
Just some explanation (since this answer seems to get relatively regular views):
Coalesce is really just a helpful cheat that accomplishes two things:
1) No need to initialize #Names with an empty string value.
2) No need to strip off an extra separator at the end.
The solution above will give incorrect results if a row has a NULL Name value (if there is a NULL, the NULL will make #Names NULL after that row, and the next row will start over as an empty string again. Easily fixed with one of two solutions:
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(#Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL
or:
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(#Names + ', ', '') +
ISNULL(Name, 'N/A')
FROM People
Depending on what behavior you want (the first option just filters NULLs out, the second option keeps them in the list with a marker message [replace 'N/A' with whatever is appropriate for you]).
SQL Server 2017+ and SQL Azure: STRING_AGG
Starting with the next version of SQL Server, we can finally concatenate across rows without having to resort to any variable or XML witchery.
STRING_AGG (Transact-SQL)
Without grouping
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
With grouping:
SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
With grouping and sub-sorting
SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
One method not yet shown via the XML data() command in SQL Server is:
Assume a table called NameList with one column called FName,
SELECT FName + ', ' AS 'data()'
FROM NameList
FOR XML PATH('')
returns:
"Peter, Paul, Mary, "
Only the extra comma must be dealt with.
As adopted from #NReilingh's comment, you can use the following method to remove the trailing comma. Assuming the same table and column names:
STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
In SQL Server 2005
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
In SQL Server 2016
you can use the FOR JSON syntax
i.e.
SELECT per.ID,
Emails = JSON_VALUE(
REPLACE(
(SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._'
)
FROM Person per
And the result will become
Id Emails
1 abc#gmail.com
2 NULL
3 def#gmail.com, xyz#gmail.com
This will work even your data contains invalid XML characters
the '"},{"_":"' is safe because if you data contain '"},{"_":"', it will be escaped to "},{\"_\":\"
You can replace ', ' with any string separator
And in SQL Server 2017, Azure SQL Database
You can use the new STRING_AGG function
In MySQL, there is a function, GROUP_CONCAT(), which allows you to concatenate the values from multiple rows. Example:
SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people
FROM users
WHERE id IN (1,2,3)
GROUP BY a
Use COALESCE - Learn more from here
For an example:
102
103
104
Then write the below code in SQL Server,
Declare #Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers
SELECT #Numbers = COALESCE(#Numbers + ',', '') + Number
FROM TableName where Number IS NOT NULL
SELECT #Numbers
The output would be:
102,103,104
PostgreSQL arrays are awesome. Example:
Create some test data:
postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');
INSERT 0 3
test=# select * from names;
name
-------
Peter
Paul
Mary
(3 rows)
Aggregate them in an array:
test=# select array_agg(name) from names;
array_agg
-------------------
{Peter,Paul,Mary}
(1 row)
Convert the array to a comma-delimited string:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
DONE
Since PostgreSQL 9.0 it is even easier, quoting from deleted answer by "horse with no name":
select string_agg(name, ',')
from names;
Oracle 11g Release 2 supports the LISTAGG function. Documentation here.
COLUMN employees FORMAT A50
SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
GROUP BY deptno;
DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.
Warning
Be careful implementing this function if there is possibility of the resulting string going over 4000 characters. It will throw an exception. If that's the case then you need to either handle the exception or roll your own function that prevents the joined string from going over 4000 characters.
In SQL Server 2005 and later, use the query below to concatenate the rows.
DECLARE #t table
(
Id int,
Name varchar(10)
)
INSERT INTO #t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d'
SELECT ID,
stuff(
(
SELECT ','+ [Name] FROM #t WHERE Id = t.Id FOR XML PATH('')
),1,1,'')
FROM (SELECT DISTINCT ID FROM #t ) t
A recursive CTE solution was suggested, but no code was provided. The code below is an example of a recursive CTE.
Note that although the results match the question, the data doesn't quite match the given description, as I assume that you really want to be doing this on groups of rows, not all rows in the table. Changing it to match all rows in the table is left as an exercise for the reader.
;WITH basetable AS (
SELECT
id,
CAST(name AS VARCHAR(MAX)) name,
ROW_NUMBER() OVER (Partition BY id ORDER BY seq) rw,
COUNT(*) OVER (Partition BY id) recs
FROM (VALUES
(1, 'Johnny', 1),
(1, 'M', 2),
(2, 'Bill', 1),
(2, 'S.', 4),
(2, 'Preston', 5),
(2, 'Esq.', 6),
(3, 'Ted', 1),
(3, 'Theodore', 2),
(3, 'Logan', 3),
(4, 'Peter', 1),
(4, 'Paul', 2),
(4, 'Mary', 3)
) g (id, name, seq)
),
rCTE AS (
SELECT recs, id, name, rw
FROM basetable
WHERE rw = 1
UNION ALL
SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw + 1
FROM basetable b
INNER JOIN rCTE r ON b.id = r.id AND b.rw = r.rw + 1
)
SELECT name
FROM rCTE
WHERE recs = rw AND ID=4
OPTION (MAXRECURSION 101)
I don't have access to a SQL Server at home, so I'm guess at the syntax here, but it's more or less:
DECLARE #names VARCHAR(500)
SELECT #names = #names + ' ' + Name
FROM Names
In SQL Server 2017 or later versions, you can use the STRING_AGG() function to generate comma-separated values. Please have a look below at one example.
SELECT
VendorId, STRING_AGG(FirstName,',') UsersName
FROM Users
WHERE VendorId != 9
GROUP BY VendorId
You need to create a variable that will hold your final result and select into it, like so.
Easiest Solution
DECLARE #char VARCHAR(MAX);
SELECT #char = COALESCE(#char + ', ' + [column], [column])
FROM [table];
PRINT #char;
In SQL Server vNext this will be built in with the STRING_AGG function. Read more about it in STRING_AGG (Transact-SQL).
A ready-to-use solution, with no extra commas:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
An empty list will result in NULL value.
Usually you will insert the list into a table column or program variable: adjust the 255 max length to your need.
(Diwakar and Jens Frandsen provided good answers, but need improvement.)
This worked for me (SQL Server 2016):
SELECT CarNamesString = STUFF((
SELECT ',' + [Name]
FROM tbl_cars
FOR XML PATH('')
), 1, 1, '')
Here is the source: https://www.mytecbits.com/
And a solution for MySQL (since this page show up in Google for MySQL):
SELECT [Name],
GROUP_CONCAT(DISTINCT [Name] SEPARATOR ',')
FROM tbl_cars
From MySQL documentation.
Using XML helped me in getting rows separated with commas. For the extra comma we can use the replace function of SQL Server. Instead of adding a comma, use of the AS 'data()' will concatenate the rows with spaces, which later can be replaced with commas as the syntax written below.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')
Here's a sample:
DECLARE #t TABLE (name VARCHAR(10))
INSERT INTO #t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM #t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
With the other answers, the person reading the answer must be aware of a specific domain table such as vehicle or student. The table must be created and populated with data to test a solution.
Below is an example that uses SQL Server "Information_Schema.Columns" table. By using this solution, no tables need to be created or data added. This example creates a comma separated list of column names for all tables in the database.
SELECT
Table_Name
,STUFF((
SELECT ',' + Column_Name
FROM INFORMATION_SCHEMA.Columns Columns
WHERE Tables.Table_Name = Columns.Table_Name
ORDER BY Column_Name
FOR XML PATH ('')), 1, 1, ''
)Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME
On top of Chris Shaffer's answer:
If your data may get repeated, such as
Tom
Ali
John
Ali
Tom
Mike
Instead of having Tom,Ali,John,Ali,Tom,Mike
You can use DISTINCT to avoid duplicates and get Tom,Ali,John,Mike:
DECLARE #Names VARCHAR(8000)
SELECT DISTINCT #Names = COALESCE(#Names + ',', '') + Name
FROM People
WHERE Name IS NOT NULL
SELECT #Names
MySQL complete example:
We have users who can have much data and we want to have an output, where we can see all users' data in a list:
Result:
___________________________
| id | rowList |
|-------------------------|
| 0 | 6, 9 |
| 1 | 1,2,3,4,5,7,8,1 |
|_________________________|
Table Setup:
CREATE TABLE `Data` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;
INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);
CREATE TABLE `User` (
`id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `User` (`id`) VALUES
(0),
(1);
Query:
SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id
DECLARE #Names VARCHAR(8000)
SELECT #name = ''
SELECT #Names = #Names + ',' + Names FROM People
SELECT SUBSTRING(2, #Names, 7998)
This puts the stray comma at the beginning.
However, if you need other columns, or to CSV a child table you need to wrap this in a scalar user defined field (UDF).
You can use XML path as a correlated subquery in the SELECT clause too (but I'd have to wait until I go back to work because Google doesn't do work stuff at home :-)
To avoid null values you can use CONCAT()
DECLARE #names VARCHAR(500)
SELECT #names = CONCAT(#names, ' ', name)
FROM Names
select #names
I really liked elegancy of Dana's answer and just wanted to make it complete.
DECLARE #names VARCHAR(MAX)
SET #names = ''
SELECT #names = #names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET #sSql = LEFT(#sSql, LEN(#sSql) - 1)
If you want to deal with nulls you can do it by adding a where clause or add another COALESCE around the first one.
DECLARE #Names VARCHAR(8000)
SELECT #Names = COALESCE(COALESCE(#Names + ', ', '') + Name, #Names) FROM People
This answer will require some privilege on the server to work.
Assemblies are a good option for you. There are a lot of sites that explain how to create it. The one I think is very well explained is this one.
If you want, I have already created the assembly, and it is possible to download the DLL file here.
Once you have downloaded it, you will need to run the following script in your SQL Server:
EXEC sp_configure 'show advanced options', 1
RECONFIGURE;
EXEC sp_configure 'clr strict security', 1;
RECONFIGURE;
CREATE Assembly concat_assembly
AUTHORIZATION dbo
FROM '<PATH TO Concat.dll IN SERVER>'
WITH PERMISSION_SET = SAFE;
GO
CREATE AGGREGATE dbo.concat (
#Value NVARCHAR(MAX)
, #Delimiter NVARCHAR(4000)
) RETURNS NVARCHAR(MAX)
EXTERNAL Name concat_assembly.[Concat.Concat];
GO
sp_configure 'clr enabled', 1;
RECONFIGURE
Observe that the path to assembly may be accessible to server. Since you have successfully done all the steps, you can use the function like:
SELECT dbo.Concat(field1, ',')
FROM Table1
Since SQL Server 2017 it is possible to use the STRING_AGG function.
I usually use select like this to concatenate strings in SQL Server:
with lines as
(
select
row_number() over(order by id) id, -- id is a line id
line -- line of text.
from
source -- line source
),
result_lines as
(
select
id,
cast(line as nvarchar(max)) line
from
lines
where
id = 1
union all
select
l.id,
cast(r.line + N', ' + l.line as nvarchar(max))
from
lines l
inner join
result_lines r
on
l.id = r.id + 1
)
select top 1
line
from
result_lines
order by
id desc
In Oracle, it is wm_concat. I believe this function is available in the 10g release and higher.
For Oracle DBs, see this question: How can multiple rows be concatenated into one in Oracle without creating a stored procedure?
The best answer appears to be by #Emmanuel, using the built-in LISTAGG() function, available in Oracle 11g Release 2 and later.
SELECT question_id,
LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id
as #user762952 pointed out, and according to Oracle's documentation http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php, the WM_CONCAT() function is also an option. It seems stable, but Oracle explicitly recommends against using it for any application SQL, so use at your own risk.
Other than that, you will have to write your own function; the Oracle document above has a guide on how to do that.
This question already has answers here:
How to concatenate text from multiple rows into a single text string in SQL Server
(47 answers)
Closed 7 years ago.
If I issue SELECT username FROM Users I get this result:
username
--------
Paul
John
Mary
but what I really need is one row with all the values separated by comma, like this:
Paul, John, Mary
How do I do this?
select
distinct
stuff((
select ',' + u.username
from users u
where u.username = username
order by u.username
for xml path('')
),1,1,'') as userlist
from users
group by username
had a typo before, the above works
This should work for you. Tested all the way back to SQL 2000.
create table #user (username varchar(25))
insert into #user (username) values ('Paul')
insert into #user (username) values ('John')
insert into #user (username) values ('Mary')
declare #tmp varchar(250)
SET #tmp = ''
select #tmp = #tmp + username + ', ' from #user
select SUBSTRING(#tmp, 0, LEN(#tmp))
good review of several approaches:
http://blogs.msmvps.com/robfarley/2007/04/07/coalesce-is-not-the-answer-to-string-concatentation-in-t-sql/
Article copy -
Coalesce is not the answer to string concatentation in T-SQL I've seen many posts over the years about using the COALESCE function to get string concatenation working in T-SQL. This is one of the examples here (borrowed from Readifarian Marc Ridey).
DECLARE #categories varchar(200)
SET #categories = NULL
SELECT #categories = COALESCE(#categories + ',','') + Name
FROM Production.ProductCategory
SELECT #categories
This query can be quite effective, but care needs to be taken, and the use of COALESCE should be properly understood. COALESCE is the version of ISNULL which can take more than two parameters. It returns the first thing in the list of parameters which is not null. So really it has nothing to do with concatenation, and the following piece of code is exactly the same - without using COALESCE:
DECLARE #categories varchar(200)
SET #categories = ''
SELECT #categories = #categories + ',' + Name
FROM Production.ProductCategory
SELECT #categories
But the unordered nature of databases makes this unreliable. The whole reason why T-SQL doesn't (yet) have a concatenate function is that this is an aggregate for which the order of elements is important. Using this variable-assignment method of string concatenation, you may actually find that the answer that gets returned doesn't have all the values in it, particularly if you want the substrings put in a particular order. Consider the following, which on my machine only returns ',Accessories', when I wanted it to return ',Bikes,Clothing,Components,Accessories':
DECLARE #categories varchar(200)
SET #categories = NULL
SELECT #categories = COALESCE(#categories + ',','') + Name
FROM Production.ProductCategory
ORDER BY LEN(Name)
SELECT #categories
Far better is to use a method which does take order into consideration, and which has been included in SQL2005 specifically for the purpose of string concatenation - FOR XML PATH('')
SELECT ',' + Name
FROM Production.ProductCategory
ORDER BY LEN(Name)
FOR XML PATH('')
In the post I made recently comparing GROUP BY and DISTINCT when using subqueries, I demonstrated the use of FOR XML PATH(''). Have a look at this and you'll see how it works in a subquery. The 'STUFF' function is only there to remove the leading comma.
USE tempdb;
GO
CREATE TABLE t1 (id INT, NAME VARCHAR(MAX));
INSERT t1 values (1,'Jamie');
INSERT t1 values (1,'Joe');
INSERT t1 values (1,'John');
INSERT t1 values (2,'Sai');
INSERT t1 values (2,'Sam');
GO
select
id,
stuff((
select ',' + t.[name]
from t1 t
where t.id = t1.id
order by t.[name]
for xml path('')
),1,1,'') as name_csv
from t1
group by id
;
FOR XML PATH is one of the only situations in which you can use ORDER BY in a subquery. The other is TOP. And when you use an unnamed column and FOR XML PATH(''), you will get a straight concatenation, with no XML tags. This does mean that the strings will be HTML Encoded, so if you're concatenating strings which may have the < character (etc), then you should maybe fix that up afterwards, but either way, this is still the best way of concatenating strings in SQL Server 2005.
building on mwigdahls answer. if you also need to do grouping here is how to get it to look like
group, csv
'group1', 'paul, john'
'group2', 'mary'
--drop table #user
create table #user (groupName varchar(25), username varchar(25))
insert into #user (groupname, username) values ('apostles', 'Paul')
insert into #user (groupname, username) values ('apostles', 'John')
insert into #user (groupname, username) values ('family','Mary')
select
g1.groupname
, stuff((
select ', ' + g.username
from #user g
where g.groupName = g1.groupname
order by g.username
for xml path('')
),1,2,'') as name_csv
from #user g1
group by g1.groupname
You can use this query to do the above task:
DECLARE #test NVARCHAR(max)
SELECT #test = COALESCE(#test + ',', '') + field2 FROM #test
SELECT field2 = #test
For detail and step by step explanation visit the following link
http://oops-solution.blogspot.com/2011/11/sql-server-convert-table-column-data.html
DECLARE #EmployeeList varchar(100)
SELECT #EmployeeList = COALESCE(#EmployeeList + ', ', '') +
CAST(Emp_UniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1
SELECT #EmployeeList
source:
http://www.sqlteam.com/article/using-coalesce-to-build-comma-delimited-string
In SQLite this is simpler. I think there are similar implementations for MySQL, MSSql and Orable
CREATE TABLE Beatles (id integer, name string );
INSERT INTO Beatles VALUES (1, "Paul");
INSERT INTO Beatles VALUES (2, "John");
INSERT INTO Beatles VALUES (3, "Ringo");
INSERT INTO Beatles VALUES (4, "George");
SELECT GROUP_CONCAT(name, ',') FROM Beatles;
you can use stuff() to convert rows as comma separated values
select
EmployeeID,
stuff((
SELECT ',' + FPProjectMaster.GroupName
FROM FPProjectInfo AS t INNER JOIN
FPProjectMaster ON t.ProjectID = FPProjectMaster.ProjectID
WHERE (t.EmployeeID = FPProjectInfo.EmployeeID)
And t.STatusID = 1
ORDER BY t.ProjectID
for xml path('')
),1,1,'') as name_csv
from FPProjectInfo
group by EmployeeID;
Thanks #AlexKuznetsov for the reference to get this answer.
A clean and flexible solution in MS SQL Server 2005/2008 is to create a CLR Agregate function.
You'll find quite a few articles (with code) on google.
It looks like this article walks you through the whole process using C#.
If you're executing this through PHP, what about this?
$hQuery = mysql_query("SELECT * FROM users");
while($hRow = mysql_fetch_array($hQuery)) {
$hOut .= $hRow['username'] . ", ";
}
$hOut = substr($hOut, 0, strlen($hOut) - 1);
echo $hOut;
Environment: Sql Server 2008
have rows of a column that contains comma separated values.
what to get the row even if a single product exists in that csv.
this is how i can do it but was just wondering if another way to write it?
SELECT * FROM REWARDS
WHERE ProductCsv like '%banana%'
or ProductCsv like '%strawberry%'
or ProductCsv like '%orange%'
Your current query doesn't seem to accurately capture the results you want. What if we have a row like this:
bananaberry cream pie,strawberry shortcake,orange juice
Should this match your query or not? I think this would be more accurate:
WHERE ',' + ProductCsv + ',' LIKE '%,banana,%'
OR ',' + ProductCsv + ',' LIKE '%,strawberry,%'
OR ',' + ProductCsv + ',' LIKE '%,orange,%'
If you're just trying to find one item, this is probably much more efficient:
WHERE ',' + ProductCsv + ',' LIKE '%,banana,%'
You probably want to use a split function. For example:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delim NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>' + REPLACE(#List, #Delim, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
So now you can say:
SELECT * FROM dbo.REWARDS AS r
WHERE EXISTS
(
SELECT 1 FROM dbo.REWARDS AS r2
CROSS APPLY dbo.SplitStrings_XML(r2.ProductCsv, ',') AS x
WHERE x.Item IN ('orange','strawberry','banana')
AND r2.[key] = r.[key]
);
Or more simply:
SELECT DISTINCT r.ProductCsv --, other columns
FROM dbo.REWARDS AS r
CROSS APPLY dbo.SplitStrings_XML(r.ProductCsv, ',') AS x
WHERE x.Item IN ('orange','strawberry','banana');
The XML approach is a little brittle depending on the kinds of strings can be stored in the table, but there are many alternatives (including passing in your set via a TVP instead of as a list or separate values). For some much deeper background on split functions see these blog posts:
http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings
http://www.sqlperformance.com/2012/08/t-sql-queries/splitting-strings-follow-up
http://www.sqlperformance.com/2012/08/t-sql-queries/splitting-strings-now-with-less-t-sql
That all said, I don't know if this is any better than what you have now.
Have a look at the following example using XML
DECLARE #Table TABLE(
CommaList VARCHAR(MAX)
)
INSERT INTO #Table SELECT 'banana,test,hello'
INSERT INTO #Table SELECT 'homer,banana,test,hello'
INSERT INTO #Table SELECT 'homer,banana'
INSERT INTO #Table SELECT '1,2,3'
;WITH XMLValues AS (
SELECT *,
CAST('<d>' + REPLACE(CommaList, ',', '</d><d>') + '</d>' AS XML) XMLValue
FROM #Table t
)
, SplitValues AS (
SELECT xv.*,
T2.Loc.value('.','VARCHAR(MAX)') SplitValue
FROM XMLValues xv
CROSS APPLY XMLValue.nodes('/d') as T2(Loc)
)
SELECT DISTINCT
CommaList
FROM SplitValues
WHERE SplitValue = 'banana'
xml Data Type Methods
nodes() Method (xml Data Type)
value() Method (xml Data Type)
Using Common Table Expressions
You could store all the values to be compared in a table
DECLARE #Product_list TABLE(
products VARCHAR(50)
)
Insert into #Product_list values
('banana'),
('strawberry'),
('orange')
SELECT * FROM REWARDS
join #Product_list
on ProductCsv like '%'+products+'%'
You could use a split function (there are many around on the web) to split ProductsCsv into individual elements and then compare that against banana, strawberry etc.
Ideally, you wouldn't store CSV data in a column, but instead have a separate table for that.