Как объединить строки строкового поля в запросе группы 'PostgreSQL'? - PullRequest
311 голосов
/ 04 сентября 2008

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

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

Ответы [ 14 ]

483 голосов
/ 04 сентября 2008

PostgreSQL 9.0 или более поздняя версия:

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

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

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

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

Или действительно:

SELECT string_agg(actor_name, ', ' ORDER BY first_appearance)

PostgreSQL 8.4 или новее:

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;

string_agg для версий до 9.0:

В случае, если кто-то сталкивается с этим в поисках прокладки совместимости для баз данных до 9.0, можно реализовать все в string_agg, кроме предложения ORDER BY.

Таким образом, с приведенным ниже определением это должно работать так же, как в 9.x Postgres DB:

SELECT string_agg(name, '; ') AS semi_colon_separated_names FROM things;

Но это будет синтаксическая ошибка:

SELECT string_agg(name, '; ' ORDER BY name) AS semi_colon_separated_names FROM things;
--> ERROR: syntax error at or near "ORDER"

Протестировано на PostgreSQL 8.3.

CREATE FUNCTION string_agg_transfn(text, text, text)
    RETURNS text AS 
    $$
        BEGIN
            IF $1 IS NULL THEN
                RETURN $2;
            ELSE
                RETURN $1 || $3 || $2;
            END IF;
        END;
    $$
    LANGUAGE plpgsql IMMUTABLE
COST 1;

CREATE AGGREGATE string_agg(text, text) (
    SFUNC=string_agg_transfn,
    STYPE=text
);

Пользовательские варианты (все версии Postgres)

До 9.0 не было встроенной агрегатной функции для объединения строк. Простейшая пользовательская реализация (, предложенная Вадждой Габо в этом сообщении , среди многих других), заключается в использовании встроенной функции 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;
88 голосов
/ 19 февраля 2010

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

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
18 голосов
/ 26 мая 2011

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

SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;
13 голосов
/ 04 сентября 2008

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

Что я не знал, так это то, что PostgreSQL позволяет вам определять ваши собственные агрегатные функции с помощью CREATE AGGREGATE

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

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

SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;
7 голосов
/ 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 ...
5 голосов
/ 03 сентября 2009

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

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
5 голосов
/ 19 мая 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;
5 голосов
/ 09 февраля 2009

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

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

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

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

3 голосов
/ 19 февраля 2009

Я нашел эту документацию 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;
2 голосов
/ 13 апреля 2019

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

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