Как объединить строки строкового поля в запрос PostgreSQL 'group by'?

Я ищу способ объединить строки поля в группе по запросу. Так, например, у меня есть таблица:

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

и я хотел сгруппировать по company_id, чтобы получить что-то вроде:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

Для этого в mySQL есть встроенная функция group_concat


person Guy C    schedule 04.09.2008    source источник
comment
Ответ Маркуса Деринга технически лучше.   -  person pstanton    schedule 01.09.2011
comment
@pstanton, ответ Деринга лучше только для 8.4 и ниже.   -  person Jared Beck    schedule 29.07.2013
comment
Этот вопрос больше подходит для dba.stackexchange.com.   -  person Dave Jarvis    schedule 04.10.2014
comment
Теперь это должен быть правильный ответ stackoverflow.com/a/47638417/243233   -  person Jus12    schedule 04.12.2017


Ответы (14)


PostgreSQL 9.0 или новее:

Современный Postgres (с 2010 г.) имеет _1 _ функция, которая будет делать именно то, что искал спрашивающий:

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

Postgres 9 также добавил возможность указывать предложение ORDER BY в любом агрегированное выражение; в противном случае вам придется упорядочить все результаты или иметь дело с неопределенным порядком. Итак, теперь вы можете написать:

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

PostgreSQL 8.4.x:

PostgreSQL 8.4 (в 2009 г.) представил совокупный функция array_agg(expression), которая собирает значения в массив. Тогда array_to_string() можно использовать для получения желаемого результата:

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

PostgreSQL 8.3.x и старше:

Когда изначально был задан этот вопрос, не существовало встроенной агрегатной функции для объединения строк. Простейшая индивидуальная реализация (, предложенная Вайдой Габо в этом сообщении списка рассылки, среди многих других) заключается в использовании встроенной функции textcat (которая находится за оператором ||):

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

Вот CREATE AGGREGATE документация.

Это просто склеивает все струны вместе без разделителя. Чтобы вставить между ними, не имея его в конце, вы можете создать свою собственную функцию конкатенации и заменить ее на текстовый код, указанный выше. Вот один, который я собрал и протестировал на 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;

Эта версия будет выводить запятую, даже если значение в строке пустое или пустое, поэтому вы получите следующий результат:

a, b, c, , e, , g

Если вы предпочитаете удалить лишние запятые, чтобы вывести это:

a, b, c, e, g

Затем добавьте ELSIF проверку к функции следующим образом:

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;
person Neall    schedule 04.09.2008
comment
Мне пришлось использовать S&R varchar для текста (последняя стабильная версия pgsql), но это здорово! - person Kev; 18.11.2008
comment
Вы можете написать функцию только на SQL, что проще для установки (plpgsql должен быть установлен суперпользователем). См. Мой пост для примера. - person bortzmeyer; 09.12.2008
comment
Нет встроенной агрегатной функции для объединения строк - почему бы вам не использовать array_to_string(array_agg(employee), ',')? - person pstanton; 01.09.2011
comment
+1 для функции PostgreSQL 9.0. Если вам нужно беспокоиться о версии до 9.0, ответ Маркуса лучше. - person Brad Koch; 04.11.2011
comment
Обратите внимание, что последние версии Postgres также позволяют использовать предложение Order By внутри агрегатной функции, например string_agg(employee, ',' Order By employee) - person IMSoP; 06.04.2013
comment
NB: вам может потребоваться преобразовать поле в текст: string_agg (something_id :: text, ',') - person igo; 27.06.2016
comment
IBM Big SQL вроде как основан на Postgres, и вы можете добиться этого с помощью чего-то похожего на array_agg (который работает, но array_to_string нет). Вместо этого используйте listagg, например: listagg(DISTINCT employee, ', '). - person Travis Heeter; 05.11.2017
comment
@Neall Я только что понял, что сильно отредактировал твой ответ, потому что думал, что он мой! Ой. Надеюсь, вы не против, но так все в одном месте без особых комментариев. - person IMSoP; 01.03.2018
comment
@Neall вы также можете удалить повторяющиеся значения, используя разные. Следующий запрос объединит только разные имена сотрудников. ВЫБЕРИТЕ company_id, string_agg (отдельный сотрудник, ',') ИЗ mytable GROUP BY company_id; - person Armando; 15.01.2020

Как насчет использования встроенных функций массива Postgres? По крайней мере, на 8.4 это работает из коробки:

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
person Markus Döring    schedule 18.02.2010
comment
к сожалению, это не работает для нас в Greenplum (v8.2). +1 все равно - person ekkis; 25.08.2016
comment
У меня отлично работает на Greenplum 4.3.4.1 (построено на PostgreSQL 8.2.15). - person PhilHibbs; 31.05.2017

Начиная с PostgreSQL 9.0, вы можете использовать агрегатную функцию, называемую string_agg. Ваш новый SQL должен выглядеть примерно так:

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

person dirbacke    schedule 26.05.2011

Я не претендую на ответ, потому что нашел его после некоторых поисков:

Я не знал, что PostgreSQL позволяет вам определять свои собственные агрегатные функции с помощью СОЗДАТЬ ОБЩИЙ

Этот пост в списке PostgreSQL показывает, насколько тривиально создавать функция для выполнения того, что требуется:

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

SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;
person Guy C    schedule 04.09.2008

Как уже упоминалось, создание собственной агрегатной функции - правильное решение. Вот моя агрегатная функция конкатенации (подробности можно найти на французском языке):

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 = ''

);

А затем используйте его как:

SELECT company_id, concatenate(employee) AS employees FROM ...
person bortzmeyer    schedule 09.12.2008

Этот последний фрагмент списка объявлений может быть интересен при обновлении до 8.4:

Пока 8.4 не выйдет с суперэффективным собственным, вы можете добавить функцию array_accum () в документацию PostgreSQL для сворачивания любого столбца в массив, который затем может использоваться кодом приложения или в сочетании с array_to_string () для форматирования. это как список:

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

Я бы дал ссылку на документацию по разработке 8.4, но они, похоже, еще не перечисляют эту функцию.

person Kev    schedule 09.02.2009

Следуя ответу Кева, используя документы Postgres:

Сначала создайте массив элементов, затем используйте встроенную функцию array_to_string.

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

select array_to_string(array_accum(name),'|') from table group by id;
person Community    schedule 19.05.2009

Следуя еще раз об использовании настраиваемой агрегатной функции конкатенации строк: вам нужно помнить, что оператор select будет размещать строки в любом порядке, поэтому вам нужно будет выполнить вспомогательный select в from с предложением order by, а затем внешний select с предложением group by для агрегирования строк, таким образом :

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
person Community    schedule 03.09.2009

Используйте функцию STRING_AGG для PostgreSQL и Google BigQuery SQL:

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
person Valentin Podkamennyi    schedule 12.04.2019

Я нашел эту документацию PostgreSQL полезной: http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html.

В моем случае я искал простой SQL для объединения поля с скобками вокруг него, если поле не пустое.

select itemid, 
  CASE 
    itemdescription WHEN '' THEN itemname 
    ELSE itemname || ' (' || itemdescription || ')' 
  END 
from items;
person Community    schedule 19.02.2009

Согласно версии PostgreSQL 9.0 и выше вы можете использовать агрегатную функцию string_agg. Ваш новый SQL должен выглядеть примерно так:

SELECT company_id, string_agg(employee, ', ')
    FROM mytable GROUP BY company_id;
person Gobinath    schedule 01.08.2017

Вы также можете использовать функцию форматирования. Что также может неявно позаботиться о преобразовании типов текста, int и т. Д.

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
person Sandip Debnath    schedule 30.08.2018
comment
Как это связано с использованием агрегата для объединения строковых значений? - person a_horse_with_no_name; 30.08.2018

Я использую Jetbrains Rider, и мне было сложно скопировать результаты из приведенных выше примеров для повторного выполнения, потому что казалось, что все это обернуто в JSON. Это объединяет их в один оператор, который было проще запустить.

select string_agg('drop table if exists "' || tablename || '" cascade', ';') 
from pg_tables where schemaname != $$pg_catalog$$ and tableName like $$rm_%$$
person Damien Sawyer    schedule 04.12.2018

Если вы используете Amazon Redshift, где string_agg не поддерживается, попробуйте использовать listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
person Gapp    schedule 15.03.2019