34

Is it possible to write a statement that selects a column from a table and converts the results to a string?

Ideally I would want to have comma separated values.

For example, say that the SELECT statement looks something like

SELECT column
FROM table
WHERE column<10

and the result is a column with values

|column|
--------
|  1   |
|  3   |
|  5   |
|  9   |

I want as a result the string "1, 3, 5, 9"

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
eddy ed
  • 917
  • 2
  • 10
  • 21
  • 1
    you want the result as a return in sql or can it be done in code say on a winform? – lemunk Apr 24 '13 at 13:23
  • 1
    Exact duplicate of [Merge row values into a CSV (a.k.a GROUP\_CONCAT for SQL Server)](http://stackoverflow.com/questions/7755815/merge-row-values-into-a-csv-a-k-a-group-concat-for-sql-server). And many other – gbn Apr 24 '13 at 13:24
  • 1
    I need the result in sql (because I want to use it as a column value for a bigger result table) – eddy ed Apr 24 '13 at 13:24

12 Answers12

75

You can do it like this:

Fiddle demo

declare @results varchar(500)

select @results = coalesce(@results + ',', '') +  convert(varchar(12),col)
from t
order by col

select @results as results

| RESULTS |
-----------
| 1,3,5,9 |
Kaf
  • 33,101
  • 7
  • 58
  • 78
  • 4
    This method is not documented to work correctly and frequently doesn't. As the various quotes from the Connect site here indicate http://stackoverflow.com/a/15163136/73226 – Martin Smith May 25 '15 at 22:10
  • 1
    That this can end up working in unexpected ways is also a [documented antipattern](//docs.microsoft.com/en-us/sql/t-sql/language-elements/select-local-variable-transact-sql#c-antipattern-use-of-recursive-variable-assignment). – Thom A Sep 01 '23 at 08:39
  • Even when this works, it's [many times slower](https://sqlblog.org/2011/03/08/t-sql-tuesday-16-this-is-not-the-aggregate-youre-looking-for) than any other set-based solution. It was always known that this is an unsupported hack, even in the 2000s when it first appeared – Panagiotis Kanavos Sep 01 '23 at 09:06
32

There is new method in SQL Server 2017:

SELECT STRING_AGG (column, ',') AS column FROM Table;

that will produce 1,3,5,9 for you

jkalamarz
  • 541
  • 5
  • 9
13
select  stuff(list,1,1,'')
from    (
        select  ',' + cast(col1 as varchar(16)) as [text()]
        from    YourTable
        for     xml path('')
        ) as Sub(list)

Example at SQL Fiddle.

Andomar
  • 232,371
  • 49
  • 380
  • 404
5
SELECT  CAST(<COLUMN Name> AS VARCHAR(3)) + ','
FROM    <TABLE Name>
FOR     XML PATH('')
5

The current accepted answer doesn't work for multiple groupings.
Try this when you need to operate on categories of column row-values.

Suppose I have the following data:

+---------+-----------+
| column1 |  column2  |
+---------+-----------+
| cat     | Felon     |
| cat     | Purz      |
| dog     | Fido      |
| dog     | Beethoven |
| dog     | Buddy     |
| bird    | Tweety    |
+---------+-----------+

And I want this as my output:

+------+----------------------+
| type |        names         |
+------+----------------------+
| cat  | Felon,Purz           |
| dog  | Fido,Beethoven,Buddy |
| bird | Tweety               |
+------+----------------------+

(If you're following along:

create table #column_to_list (column1 varchar(30), column2 varchar(30))
insert into #column_to_list
values 
('cat','Felon'),
('cat','Purz'),
('dog','Fido'),
('dog','Beethoven'),
('dog','Buddy'),
('bird','Tweety')

)

Now – I don’t want to go into all the syntax, but as you can see, this does the initial trick for us:

select ',' + cast(column2 as varchar(255)) as [text()]  
from #column_to_list sub
where column1 = 'dog'
for xml path('')
--Using "as [text()]" here is specific to the “for XML” line after our where clause and we can’t give a name to our selection, hence the weird column_name

output:

+------------------------------------------+
| XML_F52E2B61-18A1-11d1-B105-00805F49916B |
+------------------------------------------+
| ,Fido,Beethoven,Buddy                    |
+------------------------------------------+

You can see it’s limited in that it was for just one grouping (where column1 = ‘dog’) and it left a comma in the front, and additionally it’s named weird.

So, first let's handle the leading comma using the 'stuff' function and name our column stuff_list:

select stuff([list],1,1,'') as stuff_list
from (select ',' + cast(column2 as varchar(255)) as [text()]
         from #column_to_list sub
         where column1 = 'dog'
         for xml path('')
         ) sub_query([list]) 
--"sub_query([list])" just names our column as '[list]' so we can refer to it in the stuff function.  

Output:

+----------------------+
|      stuff_list      |
+----------------------+
| Fido,Beethoven,Buddy |
+----------------------+

Finally let’s just mush this into a select statement, noting the reference to the top_query alias defining which column1 we want (on the 5th line here):

select top_query.column1, 
          (select stuff([list],1,1,'') as stuff_list
         from (select ',' + cast(column2 as varchar(255)) as [text()]
                  from #column_to_list sub
                  where sub.column1 = top_query.column1
                  for xml path('')
                  ) sub_query([list])
              ) as pet_list
from  #column_to_list top_query
group by column1
order by column1

output:

+---------+----------------------+
| column1 |       pet_list       |
+---------+----------------------+
| bird    | Tweety               |
| cat     | Felon,Purz           |
| dog     | Fido,Beethoven,Buddy |
+---------+----------------------+

And we’re done.

You can read more here:

ColinMac
  • 620
  • 2
  • 9
  • 18
1

This a stab at creating a reusable column to comma separated string. In this case, I only one strings that have values and I do not want empty strings or nulls.

First I create a user defined type that is a one column table.

-- ================================
-- Create User-defined Table Type
-- ================================
USE [RSINET.MVC]
GO

-- Create the data type
CREATE TYPE [dbo].[SingleVarcharColumn] AS TABLE 
(
    data NVARCHAR(max)
)
GO

The real purpose of the type is to simplify creating a scalar function to put the column into comma separated values.

-- ================================================
-- Template generated from Template Explorer using:
-- Create Scalar Function (New Menu).SQL
--
-- Use the Specify Values for Template Parameters 
-- command (Ctrl-Shift-M) to fill in the parameter 
-- values below.
--
-- This block of comments will not be included in
-- the definition of the function.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:  Rob Peterson        
-- Create date: 8-26-2015
-- Description: This will take a single varchar column and convert it to
-- comma separated values.
-- =============================================
CREATE FUNCTION fnGetCommaSeparatedString 
(
    -- Add the parameters for the function here
    @column AS [dbo].[SingleVarcharColumn] READONLY
)
RETURNS VARCHAR(max)
AS
BEGIN
-- Declare the return variable here
DECLARE @result VARCHAR(MAX)
DECLARE @current VARCHAR(MAX)
DECLARE @counter INT
DECLARE @c CURSOR

SET @result = ''
SET @counter = 0
-- Add the T-SQL statements to compute the return value here
SET @c = CURSOR FAST_FORWARD
    FOR SELECT COALESCE(data,'') FROM @column
    OPEN @c
    FETCH NEXT FROM @c
    INTO @current
    WHILE @@FETCH_STATUS = 0
    BEGIN
        IF @result <> '' AND @current <> '' SET @result = @result + ',' + @current
        IF @result = '' AND @current <> '' SET @result = @current
    FETCH NEXT FROM @c
    INTO @current
    END
    CLOSE @c
    DEALLOCATE @c
-- Return the result of the function
RETURN @result

END
GO

Now, to use this. I select the column I want to convert to a comma separated string into the SingleVarcharColumn Type.

DECLARE @s as SingleVarcharColumn

INSERT INTO @s VALUES ('rob')
INSERT INTO @s VALUES ('paul')
INSERT INTO @s VALUES ('james')
INSERT INTO @s VALUES (null)


INSERT INTO @s
SELECT iClientID FROM [dbo].tClient

SELECT [dbo].fnGetCommaSeparatedString(@s)

To get results like this.

rob,paul,james,1,9,10,11,12,13,14,15,16,18,19,23,26,27,28,29,30,31,32,34,35,36,37,38,39,40,41,42,44,45,46,47,48,49,50,52,53,54,56,57,59,60,61,62,63,64,65,66,67,68,69,70,71,72,74,75,76,77,78,81,82,83,84,87,88,90,91,92,93,94,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159

I made my data column in my SingleVarcharColumn type an NVARCHAR(MAX) which may hurt performance, but I flexibility was what I was looking for and it runs fast enough for my purposes. It would probably be faster if it were a varchar and if it had a fixed and smaller width, but I have not tested it.

Rob Peterson
  • 175
  • 8
  • Cursor methods like this are slower than any set-based method. Aaron Bertrand [did a benchmark in 2011](https://sqlblog.org/2011/03/08/t-sql-tuesday-16-this-is-not-the-aggregate-youre-looking-for) and repeated it with more techniques over the years. Cursors, as expected, always performed many times worse than other methods – Panagiotis Kanavos Sep 01 '23 at 09:13
1

You can use the following method:

select
STUFF(
        (
        select ', ' + CONVERT(varchar(10), ID) FROM @temp
        where ID<50
group by ID for xml path('')
        ), 1, 2, '') as IDs

Implementation:

Declare @temp Table(
ID int
)
insert into @temp
(ID)
values
(1)
insert into @temp
(ID)
values
(3)
insert into @temp
(ID)
values
(5)
insert into @temp
(ID)
values
(9)

 select
STUFF(
        (
        select ', ' + CONVERT(varchar(10), ID) FROM @temp
        where ID<50
group by ID for xml path('')
        ), 1, 2, '') as IDs

Result will be:

enter image description here

Muhammad Awais
  • 4,238
  • 1
  • 42
  • 37
1

--------------------------- easy I Found Like it ----------------

SELECT STUFF((
        select ','+ name 
        from tblUsers
        FOR XML PATH('')
        )
        ,1,1,'') AS names

name
---------
mari, joan, carls
---------
Carlos
  • 572
  • 1
  • 5
  • 13
-1
ALTER PROCEDURE [dbo].[spConvertir_CampoACadena]( @nomb_tabla   varchar(30),
                          @campo_tabla  varchar(30),
                          @delimitador  varchar(5),
                          @respuesta    varchar(max) OUTPUT
)
AS
DECLARE @query      varchar(1000),
    @cadena     varchar(500)
BEGIN
  SET @query = 'SELECT @cadena  = COALESCE(@cadena + '''+ @delimitador +''', '+ '''''' +') + '+ @campo_tabla + ' FROM '+@nomb_tabla
  --select @query
  EXEC(@query)
  SET @respuesta = @cadena  
END
eebbesen
  • 5,070
  • 8
  • 48
  • 70
Huacho
  • 1
  • 1
  • This doesn't appear to be much related to the question, but even if it is, please put down some explanation of the code you have posted. Pure code dumps aren't very useful. – Ajean Sep 25 '14 at 20:03
-2

Use LISTAGG function, ex. SELECT LISTAGG(colmn) FROM table_name;

Cray
  • 2,774
  • 7
  • 22
  • 32
-2

Use simplest way of doing this-

SELECT GROUP_CONCAT(Column) from table
-2
+------+----------------------+
| type |        names         |
+------+----------------------+
| cat  | Felon                |
| cat  | Purz                 |
| dog  | Fido                 |
| dog  | Beethoven            |
| dog  | Buddy                |
| bird | Tweety               |
+------+----------------------+

select group_concat(name) from Pets
group by type

Here you can easily get the answer in single SQL and by using group by in your SQL you can separate the result based on that column value. Also you can use your own custom separator for splitting values

Result:

+------+----------------------+
| type |        names         |
+------+----------------------+
| cat  | Felon,Purz           |
| dog  | Fido,Beethoven,Buddy |
| bird | Tweety               |
+------+----------------------+
Arvind
  • 1
  • 3