Как объединить строки строкового поля в запрос 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

Ответ Маркуса Деринга технически лучше.

pstanton 01.09.2011 05:11

@pstanton, ответ Деринга лучше только для 8.4 и ниже.

Jared Beck 29.07.2013 23:32

Этот вопрос больше подходит для dba.stackexchange.com.

Dave Jarvis 04.10.2014 02:17

Теперь это должен быть правильный ответ stackoverflow.com/a/47638417/243233

Jus12 04.12.2017 21:13
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
385
4
337 052
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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

Чего я не знал, так это того, что 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;
Ответ принят как подходящий

PostgreSQL 9.0 или новее:

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

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.

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

Мне пришлось использовать S&R varchar для текста (последняя стабильная версия pgsql), но это здорово!

Kev 18.11.2008 19:26

Вы можете написать функцию только на SQL, что проще для установки (plpgsql должен быть установлен суперпользователем). См. Мой пост для примера.

bortzmeyer 09.12.2008 22:55

«Нет встроенной агрегатной функции для объединения строк» ​​- почему бы вам не использовать array_to_string(array_agg(employee), ',')?

pstanton 01.09.2011 05:11

+1 для функции PostgreSQL 9.0. Если вам нужно беспокоиться о версии до 9.0, ответ Маркуса лучше.

Brad Koch 04.11.2011 20:17

Обратите внимание, что последние версии Postgres также позволяют использовать предложение Order By внутри агрегатной функции, например string_agg(employee, ',' Order By employee)

IMSoP 06.04.2013 15:58

NB: вам может потребоваться преобразовать поле в текст: string_agg (something_id :: text, ',')

igo 27.06.2016 17:00

IBM Big SQL вроде как основан на Postgres, и вы можете добиться этого с помощью чего-то похожего на array_agg (который работает, но array_to_string нет). Вместо этого используйте listagg, например: listagg(DISTINCT employee, ', ').

Travis Heeter 05.11.2017 19:28

@Neall Я только что понял, что сильно отредактировал твой ответ, потому что думал, что он мой! Ой. Надеюсь, вы не против, но так все в одном месте без особых комментариев.

IMSoP 01.03.2018 21:05

@Neall вы также можете удалить повторяющиеся значения, используя разные. Следующий запрос объединит только разные имена сотрудников. ВЫБЕРИТЕ company_id, string_agg (отдельный сотрудник, ',') ИЗ mytable GROUP BY company_id;

Armando 15.01.2020 00:43

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

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

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

);

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

SELECT company_id, concatenate(employee) AS employees FROM ...

Этот последний фрагмент списка объявлений может быть интересен при обновлении до 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

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

Я нашел эту документацию 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;

Следуя ответу Кева, используя документы 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;

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

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

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

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

к сожалению, это не работает для нас в Greenplum (v8.2). +1 все равно

ekkis 25.08.2016 20:35

У меня отлично работает на Greenplum 4.3.4.1 (построено на PostgreSQL 8.2.15).

PhilHibbs 31.05.2017 16:28

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

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

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

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

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

Как это связано с использованием агрегата для объединения строковых значений?

a_horse_with_no_name 30.08.2018 14:37

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

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

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

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;

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

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;

Другие вопросы по теме