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

StackOverflow https://stackoverflow.com/questions/43870

Вопрос

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

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 есть встроенная функция для этого группа_concat

Это было полезно?

Решение

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 оговорка.

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

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;

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

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

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

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

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

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

Чего я не знал, так это того, что 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;

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

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 ...

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

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

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

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

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

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

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

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

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

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

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

Я использую 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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top