How to Return Query Results as a Comma Separated List in SQL Server – STRING_AGG()

Starting with SQL Server 2017, you can now make your query results appear as a list. This means you can have your result set appear as a comma-separated list, a space-separated list, or whatever separator you choose to use.

While it’s true that you could achieve this same effect prior to SQL Server 2017, it was a bit fiddly.

Transact-SQL now has the STRING_AGG() function, which concatenates the values of string expressions and places separator values between them. This works in much the same way to MySQL’s GROUP_CONCAT() function.

This article provides examples that demonstrate the T-SQL STRING_AGG() function.

Sample Data

First, here’s some sample data.

SELECT TaskId, TaskName 
FROM Tasks;

Result:

TaskId  TaskName    
------  ------------
1       Feed cats   
2       Water dog   
3       Feed garden 
4       Paint carpet
5       Clean roof  
6       Feed cats   

Example – Comma Separated List

So we could take the above data, and use the STRING_AGG() function to list all the task names in one big comma separated list.

Like this:

SELECT STRING_AGG(TaskName, ', ') 
FROM Tasks;

Result:

Feed cats, Water dog, Feed garden, Paint carpet, Clean roof, Feed cats   

Of course, it doesn’t necessarily need to be separated by a comma. It can be separated by any expression of NVARCHAR or VARCHAR type, and it can be a literal or a variable.

Example – Combining Columns

We could also use the CONCAT() function to combine two fields together, separated by their own separator.

Example:

SELECT STRING_AGG(CONCAT(TaskId, ') ', TaskName), ' ') 
FROM Tasks;

Result:

1) Feed cats 2) Water dog 3) Feed garden 4) Paint carpet 5) Clean roof 6) Feed cats

Example – Null Values

If your result set contains null values, those values are ignored and a corresponding separator is not added.

If this isn’t suitable, you can provide a value for null values by using the ISNULL() function and passing in the value you’d like to use whenever a null value is encountered. Doing this ensures that you still get a result when a row contains a null value.

For example, consider the following query and result set:

SELECT TaskCode 
FROM Tasks;

Result:

TaskCode
--------
cat123  
null    
null    
pnt456  
rof789  
null    

We can see that there are three null values within the result set.

If we run this through the STRING_AGG() function, we get this:

SELECT STRING_AGG(TaskCode, ', ') 
FROM Tasks;

Result:

cat123, pnt456, rof789

However, if we use the ISNULL() function to provide a placeholder for any null values, we get this:

SELECT STRING_AGG(ISNULL(TaskCode, 'N/A'), ', ') 
FROM Tasks;

Result:

cat123, N/A, N/A, pnt456, rof789, N/A

Example – Grouped Results

You can also use the STRING_AGG() function when grouping your result set. For example, you might want a list of albums grouped by artist.

To demonstrate this, imagine a database with two tables; Artists and Albums. There is a one to many relationship between these tables. For every artist, there could be many albums.

So a regular query joining both tables might look something like this:

USE Music;
SELECT ar.ArtistName,
	al.AlbumName
FROM Artists ar
INNER JOIN Albums al
ON ar.ArtistId = al.ArtistId;

Result:

ArtistName                 AlbumName               
-------------------------  ------------------------
Iron Maiden                Powerslave              
AC/DC                      Powerage                
Jim Reeves                 Singing Down the Lane   
Devin Townsend             Ziltoid the Omniscient  
Devin Townsend             Casualties of Cool      
Devin Townsend             Epicloud                
Iron Maiden                Somewhere in Time       
Iron Maiden                Piece of Mind           
Iron Maiden                Killers                 
Iron Maiden                No Prayer for the Dying 
The Script                 No Sound Without Silence
Buddy Rich                 Big Swing Face          
Michael Learns to Rock     Blue Night              
Michael Learns to Rock     Eternity                
Michael Learns to Rock     Scandinavia             
Tom Jones                  Long Lost Suitcase      
Tom Jones                  Praise and Blame        
Tom Jones                  Along Came Jones        
Allan Holdsworth           All Night Wrong         
Allan Holdsworth           The Sixteen Men of Tain 

As you can see, if an artist has more than one album, the artist’s name is listed multiple times – once for each album.

But we can use STRING_AGG() to change this so that we list each artist only once, followed by a comma-separated list of albums they’ve released:

USE Music;
SELECT ar.ArtistName,
	STRING_AGG(al.AlbumName, ', ')
FROM Artists ar
INNER JOIN Albums al
ON ar.ArtistId = al.ArtistId
GROUP BY ArtistName;

Result:

ArtistName                                                                                               
-------------------------  ------------------------------------------------------------------------------
AC/DC                      Powerage                                                                      
Allan Holdsworth           All Night Wrong, The Sixteen Men of Tain                                      
Buddy Rich                 Big Swing Face                                                                
Devin Townsend             Ziltoid the Omniscient, Casualties of Cool, Epicloud                          
Iron Maiden                Powerslave, Somewhere in Time, Piece of Mind, Killers, No Prayer for the Dying
Jim Reeves                 Singing Down the Lane                                                         
Michael Learns to Rock     Blue Night, Eternity, Scandinavia                                             
The Script                 No Sound Without Silence                                                      
Tom Jones                  Long Lost Suitcase, Praise and Blame, Along Came Jones                        

Example – Ordering the Results

You can use an order clause to order the results within the concatenated group. This is done with the WITHIN GROUP clause. When using this clause, you specify the order with ORDER BY followed by either ASC (for ascending) or DESC (for descending).

Example:

USE Music;
SELECT ar.ArtistName,
	STRING_AGG(al.AlbumName, ', ') WITHIN GROUP (ORDER BY al.AlbumName DESC)
FROM Artists ar
INNER JOIN Albums al
ON ar.ArtistId = al.ArtistId
GROUP BY ArtistName;

Result:

ArtistName                                                                                               
-------------------------  ------------------------------------------------------------------------------
AC/DC                      Powerage                                                                      
Allan Holdsworth           The Sixteen Men of Tain, All Night Wrong                                      
Buddy Rich                 Big Swing Face                                                                
Devin Townsend             Ziltoid the Omniscient, Epicloud, Casualties of Cool                          
Iron Maiden                Somewhere in Time, Powerslave, Piece of Mind, No Prayer for the Dying, Killers
Jim Reeves                 Singing Down the Lane                                                         
Michael Learns to Rock     Scandinavia, Eternity, Blue Night                                             
The Script                 No Sound Without Silence                                                      
Tom Jones                  Praise and Blame, Long Lost Suitcase, Along Came Jones