426

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

a_horse_with_no_name
  • 497,550
  • 91
  • 775
  • 843
Guy C
  • 6,318
  • 5
  • 28
  • 30

14 Answers14

644

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:

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 (which lies behind the || operator):

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
  • 25,118
  • 5
  • 47
  • 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; – Armando Jan 14 '20 at 21:43
110

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,127
  • 1
  • 7
  • 3
23

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,493
  • 19
  • 22
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,318
  • 5
  • 28
  • 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
  • 32,645
  • 10
  • 64
  • 90
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
  • 17,848
  • 18
  • 106
  • 133
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,113
  • 14
  • 77
  • 110
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
  • 17,848
  • 18
  • 106
  • 133
4

Use STRING_AGG function for PostgreSQL and Google BigQuery SQL:

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
Valentin Podkamennyi
  • 6,855
  • 4
  • 25
  • 43
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;
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
  • 23
  • 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
  • 745
  • 7
  • 5
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
  • 4,471
  • 3
  • 43
  • 52
0

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,274
  • 1
  • 14
  • 19
Gapp
  • 1