2380

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JohnnyM
  • 28,758
  • 10
  • 38
  • 37
  • 29
    For answers specific to SQL Server, try [this question](http://stackoverflow.com/questions/6899/is-there-a-way-to-create-a-mssql-function-to-join-multiple-rows-from-a-subquery). – Matt Hamilton Oct 12 '08 at 00:03
  • 22
    For MySQL, check out [Group_Concat](http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat) from [this answer](http://stackoverflow.com/questions/276927/can-i-concatenate-multiple-mysql-rows-into-one-field) – Pykler May 06 '11 at 19:48
  • 31
    I wish the next version of SQL Server would offer a new feature to solve multi-row string concatination elegantly without the silliness of FOR XML PATH. – Pete Alvin Oct 02 '14 at 11:47
  • 1
    step by step tutorial for describe above answers : try this article : [ http://www.sqlmatters.com/Articles/Converting%20row%20values%20in%20a%20table%20to%20a%20single%20concatenated%20string.aspx ] – saber tabatabaee yazdi Dec 27 '14 at 02:10
  • 4
    Not SQL, but if this is a once-only thing, you can paste the list into this in-browser tool [convert.town/column-to-comma-separated-list](http://convert.town/column-to-comma-separated-list) – Stack Man May 27 '15 at 07:56
  • 4
    In Oracle you can use the LISTAGG(COLUMN_NAME) from 11g r2 before that there is an unsupported function called WM_CONCAT(COLUMN_NAME) which does the same. – Richard Jul 06 '17 at 06:32
  • this CLR solution which can be plugged directly is something as same as my sql GROUP_CONCAT, [here](https://github.com/orlando-colamatteo/ms-sql-server-group-concat-sqlclr) – Biju jose May 20 '18 at 08:22
  • You can use string concatenation feature. I can't add answer (since it's locked), so I add answer here: `DECLARE @big_string varchar(max) = ''; SELECT @big_string += x.s + ',' FROM (VALUES ('string1'), ('string2'), ('string3')) AS x(s);`. Now show the result: `SELECT @big_string;`. It's that easy. – JohnyL Aug 02 '18 at 18:36
  • 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 ('') ) [Students] FROM dbo.Students ST2 ) [Main] –  Aug 03 '21 at 15:31
  • SQL licenses cost 20x more than regular Windows licenses. If possible, perform data manipulation by a CPU that you pay a lot less for, such as the web server. – Brain2000 Sep 15 '21 at 18:18

47 Answers47

1645

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
Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • 1
    I recieve an error "Incorrect syntax near the keyword 'For'" running MS SQL Server 2008 R2 – Menefee Mar 21 '12 at 18:21
  • 18
    Great solution. The following may be helpful if you need to handle special characters like those in HTML: [Rob Farley: Handling special characters with FOR XML PATH('')](http://sqlblog.com/blogs/rob_farley/archive/2010/04/15/handling-special-characters-with-for-xml-path.aspx). –  Apr 17 '13 at 12:35
  • What if there is no 'Subject ID' – JsonStatham Jul 26 '13 at 14:37
  • 14
    Apparently this doesn't work if the names contain XML characters such as `<` or `&`. See @BenHinman's comment. – Sam Aug 13 '13 at 01:26
  • This is a good solution. I concatenated 20,000 GUIDs with it in less than 1 second. `for xml path('')` performs far better than any type `cursor` and/or variable concat approach. – James L. May 23 '14 at 05:19
  • 30
    NB: This method is reliant on undocumented behavior of `FOR XML PATH ('')`. That means it should not be considered reliable as any patch or update could alter how this functions. It's basically relying on a deprecated feature. – Bacon Bits Nov 13 '14 at 18:54
  • @Bacon Bits - Could you explain that a bit more? It's an incredibly widely used piece of code. – Whelkaholism Mar 23 '15 at 10:54
  • 2
    @Whelkaholism That was Microsoft's initial response to this query. I can't seem to find their initial statement anymore (as you said, it's very widely used), but for many years after Server 2005 the SQL Server team maintained that `FOR XML PATH ('')` combined with unnamed columns has undefined behavior (i.e., the behavior is not in the design spec). Even on 2014, you will not see `FOR XML PATH ('')` used with unnamed columns in the SQL Server doc. `FOR XML PATH` with unnamed columns, yes. `FOR XML PATH ('')` with named columns, yes. But those generate different results. – Bacon Bits Mar 23 '15 at 14:00
  • 34
    @Whelkaholism The bottom line is that `FOR XML` is intended to generate XML, not concatenate arbitrary strings. That's why it escapes `&`, `<` and `>` to XML entity codes (`&`, `<`, `>`). I assume it also will escape `"` and `'` to `"` and `'` in attributes as well. It's *not* `GROUP_CONCAT()`, `string_agg()`, `array_agg()`, `listagg()`, etc. even if you can kind of make it do that. We *should* be spending our time demanding Microsoft implement a proper function. – Bacon Bits Mar 23 '15 at 14:15
  • I've to use AS 'data()' insted AS [text()]. – paio Aug 11 '15 at 13:24
  • 1
    @BaconBits [This answer](http://stackoverflow.com/a/3672860/2266979) uses better syntax which deals with special characters better by using the `TYPE` keyword and extracting the contents using the `value` XML function. Using that method, you can even use tag names and create well formed XML. But you needn't fear columns without names. They are [officially supported](https://msdn.microsoft.com/en-us/library/bb510469(v=sql.100).aspx) at least as early as SQL Server 2008. – Riley Major Oct 01 '15 at 22:00
  • 1
    To remove the initial comma, I usually use ROW_NUMBER(). Complete sample in an answer below. CASE ROW_NUMBER() OVER (ORDER BY stu.Name) WHEN 1 THEN '' ELSE ', ' END + stu.Name – Graeme Jun 01 '16 at 20:39
  • 27
    Good news: [MS SQL Server will be adding `string_agg` in v.Next.](http://stackoverflow.com/a/42778050) and all of this can go away. – Jason C Apr 06 '17 at 00:32
  • Added an improved example to address the issue pointed out by @Sam, characters <, > and & being returned as xml escape sequences <, > and &. – rrozema Nov 13 '17 at 15:29
  • You can use `STUFF` instead of `SUBSTRING` to remove the leading comma. – Jamie Kitson Nov 27 '17 at 11:12
  • This is a very poor answer! It doesn't give a simple, runnable example, nor does it explain which parts are necessary to make it work. Could someone who understands it make it better? Maybe you, @P5Coder? – jpaugh Mar 19 '18 at 14:13
  • @jpaugh why me? – Ganesh Jadhav Mar 19 '18 at 18:49
  • I picked you because you have edited it before, and probably understand it. However, I have since found [Max's answer](https://stackoverflow.com/a/48435921/712526) to fill that role, and it turns out to use the same SQL trick, although I did not recognize it at first, because of all the noise. – jpaugh Mar 19 '18 at 20:41
  • you could add to your example also this part Select Main.SubjectID, CASE WHEN Main.Students Like '%,%' THEN Left(Main.Students,Len(Main.Students)-1) ELSE Main.Students END AS [Students] – Maksym Sadovnychyy Jun 29 '18 at 09:39
  • You need to use GROUP BY instead of DISTINCT, it should give you identical results with possibly better performance. – Salman A Jan 26 '19 at 18:49
  • 1
    using `AS [text()]` make the field be surrounded by the tag ``... I just remove it and it all works (SQL Server 2008) – andynaz Feb 01 '19 at 12:00
1144

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]).

Martin Smith
  • 438,706
  • 87
  • 741
  • 845
Chris Shaffer
  • 32,199
  • 5
  • 49
  • 61
  • 84
    To be clear, coalesce has nothing to do with creating the list, it just makes sure that NULL values are not included. – Graeme Perrow Feb 13 '09 at 12:02
  • 23
    @Graeme Perrow It doesn't exclude NULL values (a WHERE is required for that -- this will *lose results* if one of the input values is NULL), and it *is required in this approach* because: NULL + non-NULL -> NULL and non-NULL + NULL -> NULL; also @Name is NULL by default and, in fact, that property is used as an implicit sentinel here to determine if a ', ' should be added or not. –  Aug 15 '10 at 18:57
  • 1
    Two ways to fix this to gracefully ignore NULLs: Either `SELECT @Names = @Names + ', ' + Name FROM People WHERE Name IS NOT NULL` or else `SELECT @Names = COALESCE(@Names + ', ' + Name, @Names) FROM People`. – krubo Jun 20 '11 at 01:14
  • 5
    @krubo No, the problem is that `@Names = @Names + *anything*` will be null because @Names is null upon declaration. The COALESCE resolves *both* null `Name` values **and** the initial null `@Names` value. – Kirk Broadhurst Aug 25 '11 at 04:34
  • 1
    This doesn't work for data types varchar and ntext, as they're both incompatible with the add operator. – XpiritO Oct 28 '11 at 14:06
  • 2
    @XpiritO - do you mean text and ntext? Varchar is compatible; text and ntext could be converted (if you are on SQL 2005 convert them to VARCHAR(MAX)/NVARCHAR(MAX) and you don't lose anything; Otherwise you'll have to accept the possibility of truncation anyway, since you can't declare a text/ntext variable). – Chris Shaffer Oct 28 '11 at 17:11
  • 72
    Please note that this method of concatenation relies on SQL Server executing the query with a particular plan. I have been caught out using this method (with the addition of an ORDER BY). When it was dealing with a small number of rows it worked fine but with more data SQL Server chose a different plan which resulted in selecting the first item with no concatenation whatsoever. See [this article](http://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/#_Toc205129492) by Anith Sen. – fbarber Apr 26 '12 at 02:18
  • 20
    This method cannot be used as a sub query in a select list or where-clause, because it use a tSQL variable. In such cases you could use the methods offered by @Ritesh – R. Schreurs Aug 02 '13 at 08:10
  • 2
    This solution will not work in a view but Ritesh's one would. – Shinigamae Oct 28 '13 at 20:09
  • 1
    This is the simplest way to build dynamic SQL if you want to apply the same command to many objects. Cade Roux uses it to [rename schemas](http://stackoverflow.com/a/317711/111424), and I use it to [rename tSQLt test classes](http://stackoverflow.com/questions/20434667/how-do-i-rename-a-tsqlt-test-class). Thanks, Chris! – Iain Samuel McLean Elder Dec 09 '13 at 00:33
  • 2
    Tried this, loved it at first because of how simple and brilliant it is. But like any other repetitive concat process to a `varchar()`, I/O and CPU eventually peg. For 20,000 GUIDs, it took 2 minutes to concatenate them, whereas using the `for xml path('')` took less than 1 second. – James L. May 23 '14 at 05:16
  • 1
    How to add Distinct to it if i only want to add up distinct values ? – confusedMind Aug 08 '14 at 13:34
  • 2
    @confusedMind - like this: `DECLARE @Names VARCHAR(8000) SELECT @Names = COALESCE(@Names + ', ', '') + Name FROM ( SELECT DISTINCT Name FROM People )` – Shay Aug 12 '14 at 00:16
  • 17
    This is not a reliable method of concatenation. It is unsupported and should not be used (per Microsoft, e.g. https://support.microsoft.com/en-us/kb/287515, https://connect.microsoft.com/SQLServer/Feedback/Details/704389). It can change without warning. Use the XML PATH technique discussed in http://stackoverflow.com/questions/5031204/does-t-sql-have-an-aggregate-function-to-concatenate-strings/5031297#5031297 I wrote more here: http://marc.durdin.net/2015/07/concatenating-strings-in-sql-server-or-undefined-behaviour-by-design/ – Marc Durdin Jul 15 '15 at 00:23
  • 2
    A Major problem with this approach is that it would truncate all values which are over 8000. – Nezam Aug 10 '15 at 18:14
  • 2
    Your explanation doesn't really explain what this does. It's relying on SQL Server executing the expression for every row. That'd be nifty if it can be relied on. But see other comments indicating that SQL Server is not required to do so for this type of query. – binki Jun 25 '16 at 15:43
  • 1
    I found the same exact code example here (posted 6 months before): http://www.codeproject.com/Tips/334400/Concatenate-many-rows-into-a-single-text-string-us I think a reference to the author should be due. – Leonardo Spina Aug 30 '16 at 11:21
  • 2
    Darn. I upvoted this answer but now that I know that it isn't the correct way to do concatenation because it can screw you over since it is an unsupported feature I can't remove my upvote anymore :-( Now I have misled many. – Jon49 Dec 21 '16 at 20:40
  • 1
    The solution works fine; however, it truncates the text at lenght 65576 – BI Dude May 10 '17 at 15:32
  • 1
    Can't use ORDER BY on this, not to mention this is horrendous advice for reasons others have posted! – MC9000 Feb 07 '19 at 01:19
  • Thanks your contribution it saved my day, Please can someone let me know if this gonna work if rows are more than Hundred or more than that ? @Martin Smith – Ibrahim Inam Jun 18 '20 at 12:16
866

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;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mathieu Renda
  • 14,069
  • 2
  • 35
  • 33
  • 4
    And, unlike CLR solutions, you have control over the sorting. – canon Jul 10 '17 at 16:17
  • There seems to be a 4000 character display limitation on STRING_AGG – InspiredBy Mar 03 '20 at 05:04
  • 1
    Is there a way to do sorting in case there is no GROUP BY (so for the "Without grouping" example)? – RuudvK May 10 '20 at 09:01
  • 1
    Update: I managed to do the following, but is there a cleaner way? SELECT STRING_AGG(Name, ', ') AS Departments FROM ( SELECT TOP 100000 Name FROM HumanResources.Department ORDER BY Name) D; – RuudvK May 10 '20 at 09:11
  • 2
    I had to cast it to NVarchar(max) to get it work.. ``` SELECT STRING_AGG(CAST(EmpName as NVARCHAR(MAX)), ',') FROM EmpTable as t ``` – Varun Sep 26 '20 at 03:25
  • RuudvK - you can still use the WITHIN GROUP syntax even if you don't have a GROUP BY clause. This allows sorting without grouping. – Sam Krygsheld Dec 01 '21 at 18:06
  • @InspiredBy I know this is from 2 years ago but STRING_AGG uses the type of the first value to determine the return type. So the max length will be determined by that type. – Lance Jul 01 '22 at 12:11
  • I missed STRING_AGG all my life – Stefan Michev Nov 02 '22 at 16:25
  • 1
    I missed `STRING_AGG` all my life. This should be the accepted answer! – Yahya Jul 05 '23 at 15:08
418

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jens frandsen
  • 4,189
  • 1
  • 14
  • 2
  • 17
    holy s**t thats amazing! When executed on its own, as in your example the result is formatted as a hyperlink, that when clicked (in SSMS) opens a new window containing the data, but when used as part of a larger query it just appears as a string. Is it a string? or is it xml that i need to treat differently in the application that will be using this data? – Ben Sep 07 '12 at 15:56
  • 10
    This approach also XML-escapes characters like < and >. So, SELECTing '' + FName + '' results in "<b>John</b><b>Paul..." – Lukáš Lánský Feb 26 '14 at 18:34
  • 8
    Neat solution. I am noticing that even when I do not add the `+ ', '` it still adds a single space between every concatenated element. – Baodad Oct 03 '14 at 22:40
  • Neat! Any ideas on how to deal with the comma at the end? – slayernoah Nov 18 '15 at 01:22
  • 9
    @Baodad That appears to be part of the deal. You can workaround by replacing on an added token character. For example, this does a perfect comma-delimited list for any length: `SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')` – NReilingh Feb 29 '16 at 18:12
  • Haha, but at that point you might as well just not use data() in the first place and just do one of the examples above. It seems `data()` is really just a shorthand for "space delimit this" and if you want something else it's useless -- unless there's a performance impact. – NReilingh Feb 29 '16 at 18:28
  • 1
    Wow, actually in my testing using data() and a replace is WAY more performant than not. Super weird. – NReilingh Feb 29 '16 at 18:33
  • 1
    If you replace data() with text() it seems to generate the list without the need to trim spaces out. – illmortem Jul 27 '17 at 21:00
  • Nice - thanks. For removing the comma, I just put the comma at the front then take `SUBSTRING( (SELECT ','+FName AS 'data()' FROM NameList FOR XML PATH('')),2,100000)` to skip the leading one. – LoztInSpace Feb 17 '20 at 22:46
  • How would you group that? say I have a two tables `people` and `roles`, a person can have many roles. I want to get a comma delimited string for each persons roles. – Spik330 Jun 17 '20 at 17:03
  • This is a nice and compact solution. But for the trim part, why not using `TRIM()` ?? E. g.: `SELECT TRIM(',' FROM (SELECT FName + ',' AS 'text()' FROM NameList FOR XML PATH('')))` – Nicolas Oct 04 '22 at 15:29
354

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

Jesse
  • 8,605
  • 7
  • 47
  • 57
teamchong
  • 1,326
  • 3
  • 16
  • 13
  • 4
    Good use of the STUFF function to nix the leading two characters. – David Aug 11 '11 at 23:12
  • 4
    I like this solution best, because I can easily use it in a select list by appending 'as – R. Schreurs Aug 02 '13 at 08:27
  • 18
    This is better than the accepted answer because this option also handles un-escaping XML reserverd characters such as `<`, `>`, `&`, etc. which `FOR XML PATH('')` will automatically escape. – BateTech Apr 07 '14 at 21:35
162

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
Pang
  • 9,564
  • 146
  • 81
  • 122
Darryl Hein
  • 142,451
  • 95
  • 218
  • 261
  • Works basically. Two things to consider: 1) if your column is not a `CHAR`, you need to cast it, e.g. via `GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')` 2) if you have many values coming, you should increase the `group_concat_max_len` as written in https://stackoverflow.com/a/1278210/1498405 – hardmooth Feb 14 '18 at 09:25
  • This worked for me as of March 2022. I had url's in rows and wanted them as a single column and this worked. Thanks! – Wilfred Almeida Mar 23 '22 at 12:20
  • 2
    OP was about [MS] SQL Server – GoldBishop Mar 26 '22 at 23:47
72

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pedram
  • 6,256
  • 10
  • 65
  • 87
66

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;
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
hgmnz
  • 13,208
  • 4
  • 37
  • 41
50

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.

Alex
  • 9,250
  • 11
  • 70
  • 81
  • 2
    For older versions of Oracle, wm_concat is perfect. Its use is explained in the link gift by Alex. Thnks Alex! – toscanelli Jul 20 '15 at 13:04
  • 1
    `LISTAGG` works perfect! Just read the document linked here. `wm_concat` removed from version 12c onwards. – asgs Jun 22 '16 at 18:56
39

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)
jmoreno
  • 12,752
  • 4
  • 60
  • 91
  • 5
    For the flabbergasted: this query inserts 12 rows (a 3 columns) into a temporary basetable, then creates a recursive Common Table Expression (rCTE) and then flattens the `name` column into a comma-separated string for 4 *groups* of `id`s. At first glance, I think this is more work than what most other solutions for SQL Server do. – knb Jul 24 '17 at 13:34
  • 5
    @knb: not sure if that is praise,condemnation,or just surprise. The base table is because I like my examples to actually work, it doesn't really have anything to do with the question. – jmoreno Jul 25 '17 at 02:20
  • 1
    I really like this solution (to iterate is human, to recurse divine!), but it will spontaneously combust if there are 100 or more entries to concatenate, due to a recursion limit in SQL Server. That's a landmine for the unwary... – RET Oct 31 '22 at 23:28
  • 1
    @RET: good point, it’s adjustable, but should be mentioned. – jmoreno Nov 01 '22 at 01:59
39

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
George G
  • 7,443
  • 12
  • 45
  • 59
Yogesh Bhadauirya
  • 1,225
  • 10
  • 10
  • 2
    I believe this fails when the values contain XML symbols such as `<` or `&`. – Sam Aug 13 '13 at 01:36
  • Works great as in examples provided. Instead of temporary table or variable I used CTE https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15 – Stritof Sep 20 '21 at 15:10
37

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
Dana
  • 32,083
  • 17
  • 62
  • 73
  • 13
    You'd need to init @names to something non-null, otherwise you will get NULL throughout; you'd also need to handle the delimiter (including the unnecessary one) – Marc Gravell Oct 12 '08 at 09:10
  • 5
    the only problem with this approach (which i use all the time) is that you can't embed it – ekkis Nov 23 '12 at 22:22
  • 3
    To get rid of the leading space change the query to `SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names` – Tian van Heerden Mar 04 '16 at 09:15
  • Also, you have to check that Name is not null, you can do it by doing: `SELECT @names = @names + ISNULL(' ' + Name, '')` – Vita1ij Mar 18 '16 at 10:49
35

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

Enter image description here

Dale K
  • 25,246
  • 15
  • 42
  • 71
sameer Ahmed
  • 537
  • 1
  • 5
  • 15
29

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;
Tigerjz32
  • 4,324
  • 4
  • 26
  • 34
24

In SQL Server vNext this will be built in with the STRING_AGG function. Read more about it in STRING_AGG (Transact-SQL).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Henrik Fransas
  • 1,067
  • 1
  • 10
  • 16
23

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Arash.Zandi
  • 1,010
  • 2
  • 13
  • 24
22

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Daniel Reis
  • 12,944
  • 6
  • 43
  • 71
19

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(''))
         , ' ', ', ') 
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Diwakar
  • 199
  • 1
  • 2
  • 2
    This is the best answer here in my opinon. The use of declare variable is no good when you need to join in another table, and this is nice and short. Good work. – David Roussel Jun 02 '11 at 16:22
  • 8
    that's not working good if FName data has spaces already, for example "My Name" – binball Jun 08 '11 at 15:16
  • Really it is working for me on ms-sql 2016 Select REPLACE( (select Name AS 'data()' from Brand Where Id IN (1,2,3,4) for xml path('')) , ' ', ', ') as allBrands – Rejwanul Reja Apr 28 '17 at 10:13
18
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
Max Szczurek
  • 4,324
  • 2
  • 20
  • 32
11

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 
Mike Barlow - BarDev
  • 11,087
  • 17
  • 62
  • 83
11

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
asmgx
  • 7,328
  • 15
  • 82
  • 143
10

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user1767754
  • 23,311
  • 18
  • 141
  • 164
  • Thanks for this! I might suggest an edit to point out the importance of the `GROUP BY` – Josh Dec 09 '21 at 19:39
8
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 :-)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
gbn
  • 422,506
  • 82
  • 585
  • 676
8

To avoid null values you can use CONCAT()

DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name) 
FROM Names
select @names
Rapunzo
  • 966
  • 5
  • 21
  • 42
8

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)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oleg Sakharov
  • 1,127
  • 2
  • 20
  • 19
  • If you are deleting the last two symbols ', ', then you need to add ', ' after Name ('SELECT \@names = \@names + Name + ', ' FROM Names'). That way the last two chars will always be ', '. – JT_ Dec 18 '15 at 11:04
  • In my case I needed to get rid of the _leading_ comma so change the query to `SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names` then you don't have to truncate it afterwards. – Tian van Heerden Mar 04 '16 at 09:13
7

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nizam
  • 4,569
  • 3
  • 43
  • 60
  • 1
    The DLL link is a 404 error. Using an assembly for this is overkill. See [best answer](https://stackoverflow.com/a/42778050/956364) for SQL Server. – Protiguous Feb 19 '20 at 13:37
6

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.

Community
  • 1
  • 1
ZeroK
  • 378
  • 3
  • 9
6

This can be useful too

create table #test (id int,name varchar(10))
--use separate inserts on older versions of SQL Server
insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack')

DECLARE @t VARCHAR(255)
SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1
select @t
drop table #test

returns

Peter,Paul,Mary
endo64
  • 2,269
  • 2
  • 27
  • 34
  • 5
    Unfortunately this behavior seems not to be officially supported. [MSDN](http://msdn.microsoft.com/en-us/library/ms187953.aspx) says: "If a variable is referenced in a select list, it should be assigned a scalar value or the SELECT statement should only return one row." And there are people who observed problems: http://sqlmag.com/sql-server/multi-row-variable-assignment-and-order – blueling Dec 05 '13 at 09:11
  • Don't forget to look https://stackoverflow.com/a/42778050/333153 On newer versions of SQL Server, we can now use STRING_AGG function. – endo64 Jan 06 '22 at 09:14
6

In Oracle, it is wm_concat. I believe this function is available in the 10g release and higher.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user762952
  • 69
  • 1
  • 1
6

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
6

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
Pramod
  • 77
  • 1
  • 3
5

SQL Server 2005 or later

CREATE TABLE dbo.Students
(
    StudentId INT
    , Name VARCHAR(50)
    , CONSTRAINT PK_Students PRIMARY KEY (StudentId)
);

CREATE TABLE dbo.Subjects
(
    SubjectId INT
    , Name VARCHAR(50)
    , CONSTRAINT PK_Subjects PRIMARY KEY (SubjectId)
);

CREATE TABLE dbo.Schedules
(
    StudentId INT
    , SubjectId INT
    , CONSTRAINT PK__Schedule PRIMARY KEY (StudentId, SubjectId)
    , CONSTRAINT FK_Schedule_Students FOREIGN KEY (StudentId) REFERENCES dbo.Students (StudentId)
    , CONSTRAINT FK_Schedule_Subjects FOREIGN KEY (SubjectId) REFERENCES dbo.Subjects (SubjectId)
);

INSERT dbo.Students (StudentId, Name) VALUES
    (1, 'Mary')
    , (2, 'John')
    , (3, 'Sam')
    , (4, 'Alaina')
    , (5, 'Edward')
;

INSERT dbo.Subjects (SubjectId, Name) VALUES
    (1, 'Physics')
    , (2, 'Geography')
    , (3, 'French')
    , (4, 'Gymnastics')
;

INSERT dbo.Schedules (StudentId, SubjectId) VALUES
    (1, 1)        --Mary, Physics
    , (2, 1)    --John, Physics
    , (3, 1)    --Sam, Physics
    , (4, 2)    --Alaina, Geography
    , (5, 2)    --Edward, Geography
;

SELECT
    sub.SubjectId
    , sub.Name AS [SubjectName]
    , ISNULL( x.Students, '') AS Students
FROM
    dbo.Subjects sub
    OUTER APPLY
    (
        SELECT
            CASE ROW_NUMBER() OVER (ORDER BY stu.Name) WHEN 1 THEN '' ELSE ', ' END
            + stu.Name
        FROM
            dbo.Students stu
            INNER JOIN dbo.Schedules sch
                ON stu.StudentId = sch.StudentId
        WHERE
            sch.SubjectId = sub.SubjectId
        ORDER BY
            stu.Name
        FOR XML PATH('')
    ) x (Students)
;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Graeme
  • 1,148
  • 12
  • 15
4

This method applies to the Teradata Aster database only as it uses its NPATH function.

Again, we have table Students

SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

Then with NPATH it is just single SELECT:

SELECT * FROM npath(
  ON Students
  PARTITION BY SubjectID
  ORDER BY StudentName
  MODE(nonoverlapping)
  PATTERN('A*')
  SYMBOLS(
    'true' as A
  )
  RESULT(
    FIRST(SubjectID of A) as SubjectID,
    ACCUMULATE(StudentName of A) as StudentName
  )
);

Result:

SubjectID       StudentName
----------      -------------
1               [John, Mary, Sam]
2               [Alaina, Edward]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
topchef
  • 19,091
  • 9
  • 63
  • 102
4

Here is the complete solution to achieve this:

-- Table Creation
CREATE TABLE Tbl
( CustomerCode    VARCHAR(50)
, CustomerName    VARCHAR(50)
, Type VARCHAR(50)
,Items    VARCHAR(50)
)

insert into Tbl
SELECT 'C0001','Thomas','BREAKFAST','Milk'
union SELECT 'C0001','Thomas','BREAKFAST','Bread'
union SELECT 'C0001','Thomas','BREAKFAST','Egg'
union SELECT 'C0001','Thomas','LUNCH','Rice'
union SELECT 'C0001','Thomas','LUNCH','Fish Curry'
union SELECT 'C0001','Thomas','LUNCH','Lessy'
union SELECT 'C0002','JOSEPH','BREAKFAST','Bread'
union SELECT 'C0002','JOSEPH','BREAKFAST','Jam'
union SELECT 'C0002','JOSEPH','BREAKFAST','Tea'
union SELECT 'C0002','JOSEPH','Supper','Tea'
union SELECT 'C0002','JOSEPH','Brunch','Roti'

-- function creation
GO
CREATE  FUNCTION [dbo].[fn_GetItemsByType]
(   
    @CustomerCode VARCHAR(50)
    ,@Type VARCHAR(50)
)
RETURNS @ItemType TABLE  ( Items VARCHAR(5000) )
AS
BEGIN

        INSERT INTO @ItemType(Items)
    SELECT  STUFF((SELECT distinct ',' + [Items]
         FROM Tbl 
         WHERE CustomerCode = @CustomerCode
            AND Type=@Type
            FOR XML PATH(''))
        ,1,1,'') as  Items



    RETURN 
END

GO

-- fianl Query
DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Type) 
                    from Tbl
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT CustomerCode,CustomerName,' + @cols + '
             from 
             (
                select  
                    distinct CustomerCode
                    ,CustomerName
                    ,Type
                    ,F.Items
                    FROM Tbl T
                    CROSS APPLY [fn_GetItemsByType] (T.CustomerCode,T.Type) F
            ) x
            pivot 
            (
                max(Items)
                for Type in (' + @cols + ')
            ) p '

execute(@query) 
Ravi Pipaliya
  • 219
  • 1
  • 9
  • An explanation would be in order. E.g., what is the gist/idea? You should also fix the indentation. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/49611871/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Aug 20 '21 at 16:56
3

One way you could do it in SQL Server would be to return the table content as XML (for XML raw), convert the result to a string and then replace the tags with ", ".

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Manu
  • 28,753
  • 28
  • 75
  • 83
3

Not that I have done any analysis on performance as my list had less than 10 items but I was amazed after looking through the 30 odd answers I still had a twist on a similar answer already given similar to using COALESCE for a single group list and didn't even have to set my variable (defaults to NULL anyhow) and it assumes all entries in my source data table are non blank:

DECLARE @MyList VARCHAR(1000), @Delimiter CHAR(2) = ', '
SELECT @MyList = CASE WHEN @MyList > '' THEN @MyList + @Delimiter ELSE '' END + FieldToConcatenate FROM MyData

I am sure COALESCE internally uses the same idea. Let’s hope Microsoft don't change this on me.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Glen
  • 802
  • 1
  • 11
  • 27
3

Below is a simple PL/SQL procedure to implement the given scenario using "basic loop" and "rownum"

Table definition

CREATE TABLE "NAMES" ("NAME" VARCHAR2(10 BYTE))) ;

Let's insert values into this table

INSERT INTO NAMES VALUES('PETER');
INSERT INTO NAMES VALUES('PAUL');
INSERT INTO NAMES VALUES('MARY');

Procedure starts from here

DECLARE 

MAXNUM INTEGER;
CNTR INTEGER := 1;
C_NAME NAMES.NAME%TYPE;
NSTR VARCHAR2(50);

BEGIN

SELECT MAX(ROWNUM) INTO MAXNUM FROM NAMES;

LOOP

SELECT NAME INTO  C_NAME FROM 
(SELECT ROWNUM RW, NAME FROM NAMES ) P WHERE P.RW = CNTR;

NSTR := NSTR ||','||C_NAME;
CNTR := CNTR + 1;
EXIT WHEN CNTR > MAXNUM;

END LOOP;

dbms_output.put_line(SUBSTR(NSTR,2));

END;

Result

PETER,PAUL,MARY
jpaugh
  • 6,634
  • 4
  • 38
  • 90
Pooja Bhat
  • 69
  • 5
  • The question is asking for an answer specific to SQL Server. If there is a PL/SQL question, you might answer there, instead. However, check out [wm_concat](http://www.dba-oracle.com/t_wm_concat_sql_function.htm), first, and see whether that is an easier method. – jpaugh Mar 19 '18 at 13:58
2

In PostgreSQL - array_agg

SELECT array_to_string(array_agg(DISTINCT rolname), ',') FROM pg_catalog.pg_roles;

Or STRING_AGG

SELECT STRING_AGG(rolname::text,',') FROM pg_catalog.pg_roles;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
panser
  • 1,949
  • 22
  • 16
1

There are a couple of ways in Oracle:

    create table name
    (first_name varchar2(30));

    insert into name values ('Peter');
    insert into name values ('Paul');
    insert into name values ('Mary');

Solution is 1:

    select substr(max(sys_connect_by_path (first_name, ',')),2) from (select rownum r, first_name from name ) n start with r=1 connect by prior r+1=r
    o/p=> Peter,Paul,Mary

Solution is 2:

    select  rtrim(xmlagg (xmlelement (e, first_name || ',')).extract ('//text()'), ',') first_name from name
    o/p=> Peter,Paul,Mary
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

With the 'TABLE' type it is extremely easy. Let's imagine that your table is called Students and it has column name.

declare @rowsCount INT
declare @i INT = 1
declare @names varchar(max) = ''

DECLARE @MyTable TABLE
(
  Id int identity,
  Name varchar(500)
)
insert into @MyTable select name from Students
set @rowsCount = (select COUNT(Id) from @MyTable)

while @i < @rowsCount
begin
 set @names = @names + ', ' + (select name from @MyTable where Id = @i)
 set @i = @i + 1
end
select @names

This example was tested with SQL Server 2008 R2.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Max Tkachenko
  • 504
  • 1
  • 6
  • 17
1
SELECT PageContent = Stuff(
    (   SELECT PageContent
        FROM dbo.InfoGuide
        WHERE CategoryId = @CategoryId
          AND SubCategoryId = @SubCategoryId
        for xml path(''), type
    ).value('.[1]','nvarchar(max)'),
    1, 1, '')
FROM dbo.InfoGuide info
Andre Figueiredo
  • 12,930
  • 8
  • 48
  • 74
Muhammad Bilal
  • 1,008
  • 13
  • 14
  • An explanation would be in order. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/37459266/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Aug 20 '21 at 16:41
1

Although it's too late, and already has many solutions. Here is simple solution for MySQL:

SELECT t1.id,
        GROUP_CONCAT(t1.id) ids
 FROM table t1 JOIN table t2 ON (t1.id = t2.id)
 GROUP BY t1.id
sheldonzy
  • 5,505
  • 9
  • 48
  • 86
Shahbaz
  • 73
  • 8
  • 1
    This question is specific to SQL server, so this answer is unlikely to be found by those who need it. Is there a mysql-specific question about the same thing? – jpaugh Mar 19 '18 at 13:59
  • @jpaugh: Not an excuse, but the 4th answer (2nd if deleted answers are not counted), posted on day two, with 140 upvotes, was also about MySQL: [Darryl Hein's answer](https://stackoverflow.com/questions/194852/how-to-concatenate-text-from-multiple-rows-into-a-single-text-string-in-sql-serv/194875#194875). Other answers are for Oracle and [PostgreSQL](https://en.wikipedia.org/wiki/PostgreSQL). What is the best way to proceed? Flag them as "not an answer"? Or something else? – Peter Mortensen Aug 20 '21 at 16:51
1

With a recursive query you can do it:

-- Create example table
CREATE TABLE tmptable (NAME VARCHAR(30)) ;

-- Insert example data
INSERT INTO tmptable VALUES('PETER');
INSERT INTO tmptable VALUES('PAUL');
INSERT INTO tmptable VALUES('MARY');

-- Recurse query
with tblwithrank as (
select * , row_number() over(order by name) rang , count(*) over() NbRow
from tmptable
),
tmpRecursive as (
select *, cast(name as varchar(2000)) as AllName from tblwithrank  where rang=1
union all
select f0.*,  cast(f0.name + ',' + f1.AllName as varchar(2000)) as AllName 
from tblwithrank f0 inner join tmpRecursive f1 on f0.rang=f1.rang +1 
)
select AllName from tmpRecursive
where rang=NbRow
Esperento57
  • 16,521
  • 3
  • 39
  • 45
1

Use this:

ISNULL(SUBSTRING(REPLACE((select ',' FName as 'data()' from NameList for xml path('')), ' ,',', '), 2, 300), '') 'MyList'

Where the "300" could be any width taking into account the maximum number of items you think will show up.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hans Bluh
  • 27
  • 1
0

We can use RECUSRSIVITY, WITH CTE, union ALL as follows

declare @mytable as table(id int identity(1,1), str nvarchar(100))
insert into @mytable values('Peter'),('Paul'),('Mary')

declare @myresult as table(id int,str nvarchar(max),ind int, R# int)

;with cte as(select id,cast(str as nvarchar(100)) as str, cast(0 as int) ind from @mytable
union all
select t2.id,cast(t1.str+',' +t2.str as nvarchar(100)) ,t1.ind+1 from cte t1 inner join @mytable t2 on t2.id=t1.id+1)
insert into @myresult select *,row_number() over(order by ind) R# from cte

select top 1 str from @myresult order by R# desc
Kemal AL GAZZAH
  • 967
  • 6
  • 15
0

First of all you should declare a table variable and fill it with your table data and after that, with a WHILE loop, select row one by one and add its value to a nvarchar(max) variable.

    Go
    declare @temp table(
        title nvarchar(50)
    )
    insert into @temp(title)
    select p.Title from dbo.person p
    --
    declare @mainString nvarchar(max)
    set @mainString = '';
    --
    while ((select count(*) from @temp) != 0)
    begin
        declare @itemTitle nvarchar(50)
        set @itemTitle = (select top(1) t.Title from @temp t)
    
        if @mainString = ''
        begin
            set @mainString = @itemTitle
        end
        else
        begin
            set @mainString = concat(@mainString,',',@itemTitle)
        end
    
        delete top(1) from @temp
    
    end
    print @mainString
MeanGreen
  • 3,098
  • 5
  • 37
  • 63
-5
   declare @phone varchar(max)='' 
   select @phone=@phone + mobileno +',' from  members
   select @phone
Hamid Bahmanabady
  • 665
  • 1
  • 8
  • 20
  • Why not `+', '` As OP wanted and also you don't delete last ';'. I think [this answer](http://stackoverflow.com/a/8206256/4519059) is same and also [this answer](http://stackoverflow.com/a/198333/4519059) ;). – shA.t Apr 20 '15 at 07:05
  • I had this problem and I found answer but I want Concatenate with ';' so I paste it here, last element is empty – Hamid Bahmanabady Apr 20 '15 at 13:24
  • 4
    When you post your answer here, It should be related to the question And result of your code should be `Null`, because you start with `@phone IS Null` and adding to `Null` will be `Null` in SQL Server, I think you forgot something like adding `= ''` after your first line ;). – shA.t Apr 20 '15 at 13:43
  • No, I post answer after check it and result was not null – Hamid Bahmanabady Apr 20 '15 at 17:36