107

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?

Richard Stelling
  • 25,607
  • 27
  • 108
  • 188
Pavel Bastov
  • 6,911
  • 7
  • 39
  • 48

10 Answers10

148
 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

Hogan
  • 69,564
  • 10
  • 76
  • 117
  • 3
    You are a genius. Also, this puts one space in front of the list. Make the STUFF options 1, 2 and it'll remove that space. I need to figure out how the hell this works, now. – Jared Mar 28 '13 at 12:58
  • @Jared - Thanks, if you have a space then you must have had `', '` and not `','` as the first item in the select. Key to understanding how it works, understand how SQL Server converts between XML and relational data. Good luck. – Hogan Mar 28 '13 at 14:35
  • Ahh yeah you're exactly right. This is my first time using the STUFF function and didn't realize what it was actually doing! Thanks! – Jared Mar 29 '13 at 01:52
  • Just wanted to say this really helped me. Thanks! – Kulingar Jun 29 '13 at 20:09
  • Best option when having to include it in a large sql, thanks! – VSP Sep 20 '13 at 08:00
  • 4
    Disclaimer: this **will not work** for data that has special characters such as `<` or `&` in it. – keeehlan Apr 17 '14 at 19:19
  • @kehrk - is there a fix for that ? – Erran Morad Apr 20 '14 at 18:11
  • 6
    @BoratSagdiyev - kehrk is wrong, `FOR XML` auto encodes, see http://sqlfiddle.com/#!6/b824a/1 – Hogan Apr 20 '14 at 19:21
  • 4
    @kehrk - ummm... no. Works fine. http://sqlfiddle.com/#!6/b824a/1 – Hogan Apr 20 '14 at 19:22
  • This is great, especially because you can use in line with an existing TSQL statement and make this just be another column in the statement. Also allows the WHERE statement to be feed a value from the TSQL statement itself. Many thanks. – famousKaneis Oct 02 '14 at 13:04
  • 3
    Should be the accepted answer –  Nov 29 '18 at 13:53
  • For my use case, this performs much better if the `where` clause is duplicated so that it's also included in the outer `select` (otherwise it evaluates for every row in the table) – Daniel Nov 06 '20 at 15:48
  • distict is not working perfectly, can see the duplicate data in result. http://sqlfiddle.com/#!18/8c053b/1 – Ram Singh Dec 21 '22 at 18:25
  • 1
    @RamSingh - http://sqlfiddle.com/#!18/8c053b/5/0 – Hogan Dec 21 '22 at 21:12
  • @RamSingh - also there was a prior comment that this will not work well for data that has character considered "special" by xml. – Hogan Dec 21 '22 at 21:17
  • @Hogan, yes, if you remove the special characters from the data, even though it is not removing duplicated data. – Ram Singh Dec 21 '22 at 21:32
114

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))
mwigdahl
  • 16,268
  • 7
  • 50
  • 64
  • 1
    +1, but `select SUBSTRING(@tmp, 0, LEN(@tmp))` looks incorrect (to me) while apparently working (I tried it). The...turgid...prose of the [MSDN page](http://msdn.microsoft.com/en-us/library/ms187748.aspx) on `substring` fails to clarify why it works, but I guess the end point is `start_expression + length_expression` without correcting `start_expression`, and since if you start with a number less than `1` it starts with "the first character" (e.g., `1`), I guess it sort of works by the back door. I think I'll use `select SUBSTRING(@tmp, 1, LEN(@tmp) - 1)` instead, though. – T.J. Crowder Aug 07 '11 at 11:53
  • Yeah, apparently, since `select SUBSTRING('testing', -2, 5)` gives us `'te'` (e.g., exactly what `select SUBSTRING('testing', 1, 2)` would give us), as in both cases the resulting (exclusive) end index is `3`. Not behavior I'd want to rely on. Is there some specific reason you do? – T.J. Crowder Aug 07 '11 at 11:57
  • No special reason, just the fact that my roots are in C++ so I'm used to zero-offset arithmetic... – mwigdahl Aug 07 '11 at 14:36
  • @T.J.Crowder - In fact because the goal is to remove the last character I believe `stuff(str,len(str)-1,1,'')` is faster. – Hogan Oct 28 '12 at 12:04
  • @mwigdahl - I seems that `select @tmp = @tmp + username + ', ' from #user` does a row by row iteration. I was expecting that to cause an error instead. How does it really work ? – Erran Morad Apr 18 '14 at 18:43
  • 1
    @BoratSagdiyev -- You are correct, that's exactly what it's doing. That syntax expands into what is effectively a mini-cursor. In general, the FOR XML PATH solution Hogan provides below is faster for large cases and if you don't care about XML escaping of your special characters. – mwigdahl Apr 18 '14 at 20:11
  • @mwigdahl - I used your code in one of my answers. One of the other answerers said this about your code - `The fact that the code you used worked is an unwanted side-effect of something else and is not supported by MS, meaning that it might stop working in some future version of SQL Server. Also, the order is not guaranteed.` https://stackoverflow.com/questions/23159179/transpose-pivot-rows-to-columns-in-sql-server – Erran Morad Apr 19 '14 at 03:20
  • Was really missing my GROUP_CONCAT() function. This rocks man! – Richard Varno Dec 17 '14 at 21:12
  • 5
    SQL server 2017/ SQL Azure supports STRING_AGG for the purpose - https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql https://stackoverflow.com/a/42778050/1545569 – Chirag Rupani Dec 25 '17 at 11:13
  • Why does it not work when you add a complex `ORDER BY`? – Salman A Oct 22 '19 at 13:31
58

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.

Brian
  • 25,523
  • 18
  • 82
  • 173
A-K
  • 16,804
  • 8
  • 54
  • 74
  • 1
    Alex, I added the whole article in case original link goes dead. I hope you will accept the changes. Thanks. Chenqui. – Erran Morad Apr 18 '14 at 18:46
  • 2
    @AlexKuznetsov - FYI - This article is over 7 years old and contains information which has not been true since SQL 2008 came out. – Hogan Apr 20 '14 at 19:27
  • @Hogan the answer is very old as well, and it was written at the time when 2008 was not widely adopted at all. I see no value in keeping this answer up-to-date. – A-K Apr 21 '14 at 15:46
  • @AlexKuznetsov the last one worked me to use in an inline query. thanks for the details answer. – Ram May 30 '14 at 10:36
  • 1
    +1 for your use of the FOR XML('') version, I was struggling to get aggregated rows partitioned by the id. Your example not only fixed it, but I understand how it works. – Morvael Jun 02 '17 at 11:02
  • Anyway to add "and" to the last comma separated item? – Irish Redneck Aug 19 '22 at 22:31
  • Interesting! When I asked about delimiting, the DBA's kept telling me to "stuff it". Seems I originally misinterpreted them. I have to go apologize now... – FloverOwe Sep 23 '22 at 19:59
12

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
Tom McDonald
  • 1,532
  • 2
  • 18
  • 37
8

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

LPL
  • 16,827
  • 6
  • 51
  • 95
Rashmi Kant
  • 159
  • 1
  • 2
7
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

LPL
  • 16,827
  • 6
  • 51
  • 95
vinay
  • 71
  • 1
  • 1
5

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.

Ram
  • 15,908
  • 4
  • 48
  • 41
4

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;
elcuco
  • 8,948
  • 9
  • 47
  • 69
  • Question was tagged as `tsql`, ie MS SQL Server. `GROUP_CONCAT` doesn't exist in SQL Server. Since SQL Server 2017 there is a `STRING_AGG` function, however, which has similar functionality. – Simon Elms Jan 27 '23 at 08:36
3

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#.

Arjan Einbu
  • 13,543
  • 2
  • 56
  • 59
-5

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;
James Brooks
  • 1,281
  • 5
  • 17
  • 28