473

I am looking for a way to concatenate the strings of a field within a group by query. So for example, I have a table:

ID COMPANY_ID EMPLOYEE
1 1 Anna
2 1 Bill
3 2 Carol
4 2 Dave

and I wanted to group by company_id to get something like:

COMPANY_ID EMPLOYEE
1 Anna, Bill
2 Carol, Dave

There is a built-in function in mySQL to do this group_concat

Anonymous
  • 835
  • 1
  • 5
  • 21
Guy C
  • 6,970
  • 5
  • 30
  • 30

14 Answers14

701

PostgreSQL 9.0 or later:

Modern Postgres (since 2010) has the string_agg(expression, delimiter) function which will do exactly what the asker was looking for:

SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;

Postgres 9 also added the ability to specify an ORDER BY clause in any aggregate expression; otherwise you have to order all your results or deal with an undefined order. So you can now write:

SELECT company_id, string_agg(employee, ', ' ORDER BY employee)
FROM mytable
GROUP BY company_id;

PostgreSQL 8.4.x:

Please note that support for Postgres 8.4 ended in 2014, so you should probably upgrade for more important reasons than string aggregation.

PostgreSQL 8.4 (in 2009) introduced the aggregate function array_agg(expression) which collects the values in an array. Then array_to_string() can be used to give the desired result:

SELECT company_id, array_to_string(array_agg(employee), ', ')
FROM mytable
GROUP BY company_id;

PostgreSQL 8.3.x and older:

When this question was originally posed, there was no built-in aggregate function to concatenate strings. The simplest custom implementation (suggested by Vajda Gabo in this mailing list post, among many others) is to use the built-in textcat function:

CREATE AGGREGATE textcat_all(
  basetype    = text,
  sfunc       = textcat,
  stype       = text,
  initcond    = ''
);

Here is the CREATE AGGREGATE documentation.

This simply glues all the strings together, with no separator. In order to get a ", " inserted in between them without having it at the end, you might want to make your own concatenation function and substitute it for the "textcat" above. Here is one I put together and tested on 8.3.12:

CREATE FUNCTION commacat(acc text, instr text) RETURNS text AS $$
  BEGIN
    IF acc IS NULL OR acc = '' THEN
      RETURN instr;
    ELSE
      RETURN acc || ', ' || instr;
    END IF;
  END;
$$ LANGUAGE plpgsql;

This version will output a comma even if the value in the row is null or empty, so you get output like this:

a, b, c, , e, , g

If you would prefer to remove extra commas to output this:

a, b, c, e, g

Then add an ELSIF check to the function like this:

CREATE FUNCTION commacat_ignore_nulls(acc text, instr text) RETURNS text AS $$
  BEGIN
    IF acc IS NULL OR acc = '' THEN
      RETURN instr;
    ELSIF instr IS NULL OR instr = '' THEN
      RETURN acc;
    ELSE
      RETURN acc || ', ' || instr;
    END IF;
  END;
$$ LANGUAGE plpgsql;
Neall
  • 26,428
  • 5
  • 49
  • 48
  • 1
    I had to S&R varchar to text (latest pgsql stable) but this is great! – Kev Nov 18 '08 at 16:26
  • 1
    You can write the function in SQL only, which is easier for installation (plpgsql has to be installed by the superuser). See my post for an example. – bortzmeyer Dec 09 '08 at 19:55
  • 11
    "There is no built-in aggregate function to concatenate strings" - why wouldn't you use `array_to_string(array_agg(employee), ',')`? – pstanton Sep 01 '11 at 01:11
  • 2
    +1 for the PostgreSQL 9.0 function. If you need to be concerned about pre-9.0, Markus's answer is better. – Brad Koch Nov 04 '11 at 16:17
  • 7
    Note that recent versions of Postgres also allow an `Order By` clause inside the aggregate function, e.g. `string_agg(employee, ',' Order By employee)` – IMSoP Apr 06 '13 at 11:58
  • NB: You may need to convert field to text: string_agg(something_id::text, ', ') – igo Jun 27 '16 at 14:00
  • IBM's Big SQL is sorta based on Postgres, and you can accomplish this with something similar to `array_agg` (which works, but `array_to_string` does not). Use `listagg` instead, like so: `listagg(DISTINCT employee, ', ')`. – Travis Heeter Nov 05 '17 at 16:28
  • @Neall I just realised I've heavily edited your answer because I thought it was mine! Oops. I hope you don't mind, but this way everything's in one place without so much commentary. – IMSoP Mar 01 '18 at 18:05
  • @Neall you can also remove duplicate values by using distinct. The following query will only concatenate different employee names. SELECT company_id, string_agg( distinct employee, ', ') FROM mytable GROUP BY company_id; – Way Too Simple Jan 14 '20 at 21:43
113

How about using Postgres built-in array functions? At least on 8.4 this works out of the box:

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
Markus Döring
  • 1,157
  • 1
  • 7
  • 3
24

As from PostgreSQL 9.0 you can use the aggregate function called string_agg. Your new SQL should look something like this:

SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;
dirbacke
  • 2,861
  • 21
  • 25
15

I claim no credit for the answer because I found it after some searching:

What I didn't know is that PostgreSQL allows you to define your own aggregate functions with CREATE AGGREGATE

This post on the PostgreSQL list shows how trivial it is to create a function to do what's required:

CREATE AGGREGATE textcat_all(
  basetype    = text,
  sfunc       = textcat,
  stype       = text,
  initcond    = ''
);

SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;
Guy C
  • 6,970
  • 5
  • 30
  • 30
7

As already mentioned, creating your own aggregate function is the right thing to do. Here is my concatenation aggregate function (you can find details in French):

CREATE OR REPLACE FUNCTION concat2(text, text) RETURNS text AS '
    SELECT CASE WHEN $1 IS NULL OR $1 = \'\' THEN $2
            WHEN $2 IS NULL OR $2 = \'\' THEN $1
            ELSE $1 || \' / \' || $2
            END; 
'
 LANGUAGE SQL;

CREATE AGGREGATE concatenate (
  sfunc = concat2,
  basetype = text,
  stype = text,
  initcond = ''

);

And then use it as:

SELECT company_id, concatenate(employee) AS employees FROM ...
bortzmeyer
  • 34,164
  • 12
  • 67
  • 91
5

Following yet again on the use of a custom aggregate function of string concatenation: you need to remember that the select statement will place rows in any order, so you will need to do a sub select in the from statement with an order by clause, and then an outer select with a group by clause to aggregate the strings, thus:

SELECT custom_aggregate(MY.special_strings)
FROM (SELECT special_strings, grouping_column 
        FROM a_table 
        ORDER BY ordering_column) MY
GROUP BY MY.grouping_column
Brad Koch
  • 19,267
  • 19
  • 110
  • 137
5

This latest announcement list snippet might be of interest if you'll be upgrading to 8.4:

Until 8.4 comes out with a super-effient native one, you can add the array_accum() function in the PostgreSQL documentation for rolling up any column into an array, which can then be used by application code, or combined with array_to_string() to format it as a list:

http://www.postgresql.org/docs/current/static/xaggr.html

I'd link to the 8.4 development docs but they don't seem to list this feature yet.

Kev
  • 15,899
  • 15
  • 79
  • 112
5

Use STRING_AGG function for PostgreSQL and Google BigQuery SQL:

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
Valentin Podkamennyi
  • 7,161
  • 4
  • 29
  • 44
5

Following up on Kev's answer, using the Postgres docs:

First, create an array of the elements, then use the built-in array_to_string function.

CREATE AGGREGATE array_accum (anyelement)
(
 sfunc = array_append,
 stype = anyarray,
 initcond = '{}'
);

select array_to_string(array_accum(name),'|') from table group by id;
Brad Koch
  • 19,267
  • 19
  • 110
  • 137
3

I found this PostgreSQL documentation helpful: http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html.

In my case, I sought plain SQL to concatenate a field with brackets around it, if the field is not empty.

select itemid, 
  CASE 
    itemdescription WHEN '' THEN itemname 
    ELSE itemname || ' (' || itemdescription || ')' 
  END 
from items;
2

If you are on Amazon Redshift, where string_agg is not supported, try using listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Arnaud Peralta
  • 1,287
  • 1
  • 16
  • 20
Gapp
  • 21
  • 1
0

According to version PostgreSQL 9.0 and above you can use the aggregate function called string_agg. Your new SQL should look something like this:

SELECT company_id, string_agg(employee, ', ')
    FROM mytable GROUP BY company_id;
Gobinath
  • 43
  • 1
  • 1
  • 5
0

You can also use format function. Which can also implicitly take care of type conversion of text, int, etc by itself.

create or replace function concat_return_row_count(tbl_name text, column_name text, value int)
returns integer as $row_count$
declare
total integer;
begin
    EXECUTE format('select count(*) from %s WHERE %s = %s', tbl_name, column_name, value) INTO total;
    return total;
end;
$row_count$ language plpgsql;


postgres=# select concat_return_row_count('tbl_name','column_name',2); --2 is the value
Sandip Debnath
  • 995
  • 8
  • 7
  • 2
    How is this related to using an aggregate to concatenate string values? –  Aug 30 '18 at 11:37
0

I'm using Jetbrains Rider and it was a hassle copying the results from above examples to re-execute because it seemed to wrap it all in JSON. This joins them into a single statement that was easier to run

select string_agg('drop table if exists "' || tablename || '" cascade', ';') 
from pg_tables where schemaname != $$pg_catalog$$ and tableName like $$rm_%$$
Damien Sawyer
  • 5,323
  • 3
  • 44
  • 56