Як об'єднати рядки рядкового поля в групу PostgreSQL 'за запитом?


351

Я шукаю спосіб об'єднати рядки поля в групу за запитом. Так, наприклад, у мене є таблиця:

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


1
Відповідь Маркуса Дорінга технічно краща.
pstanton

@pstanton, відповідь Дерінга краща лише для 8,4 і нижче.
Джаред Бек

Здається, це питання краще підходить для dba.stackexchange.com .
Дейв Джарвіс

Це повинно бути дійсним відповіддю Тепер stackoverflow.com/a/47638417/243233
Jus12

Відповіді:


542

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 для версій до 8.4:

У разі, якщо хтось стикається з цим, шукаючи сукупність сумісності для баз даних до 9.0, можливо реалізувати все, string_aggкрім ORDER BYпункту.

Отже, із нижченаведеним визначенням це має працювати так само, як у 9.x БД Postgres:

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документація.

Це просто склеює всі пасма разом, без поділу. Для того, щоб вставити "", вставлений між ними, не маючи його в кінці, ви можете зробити свою власну функцію конкатенації та замінити її на "текстовий кот" вище. Ось один я зібрав і випробував 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;

1
Мені довелося вархати S&R до тексту (останній стабільний pgsql), але це чудово!
Кев

1
Ви можете записати функцію лише в SQL, що простіше в установці (plpgsql повинен бути встановлений суперрусером). Дивіться приклад в моєму дописі.
борцмейєр

11
"Немає вбудованої сукупної функції для об'єднання рядків" - чому б ви не використали array_to_string(array_agg(employee), ',')?
pstanton

2
+1 для функції PostgreSQL 9.0. Якщо вам потрібно потурбуватися про попередні 9.0, відповідь Маркуса краще.
Бред Кох

7
Зауважте, що останні версії Postgres також дозволяють Order Byпункт у сукупності функції, наприкладstring_agg(employee, ',' Order By employee)
IMSoP

98

Як щодо використання функцій вбудованого масиву Postgres? Принаймні 8.4 це не вдається:

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

на жаль, це не працює для нас на Greenplum (v8.2). +1 все те саме
ekkis

Для мене добре працює на Greenplum 4.3.4.1 (побудований на PostgreSQL 8.2.15).
PhilHibbs

19

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

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


13

Я не претендую на відповідь, оскільки я знайшов її після певного пошуку:

Що я не знав, це те, що 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

Як уже було сказано, створення власної сукупної функції - це правильно зробити. Ось моя функція сукупності конкатенації (ви можете знайти деталі французькою мовою ):

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

Цей останній фрагмент списку оголошень може зацікавити, якщо ви перейдете до 8.4:

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

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

Я б посилався на 8.4 розробників, але ця функція, здається, ще не перерахована.


5

Слідкуйте за відповіддю Кева, використовуючи документи 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

Знову ж таки, після використання спеціальної функції сукупності об'єднання рядків: вам потрібно пам’ятати, що оператор select буде розміщувати рядки в будь-якому порядку, тому вам потрібно буде виконати підбір в операторі from з наказом за пунктом, і потім зовнішній вибір з групою за допомогою пункту для об'єднання рядків, таким чином:

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

3

Я вважаю цю документацію 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;


0

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

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

0

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

1
Як це пов'язано з використанням сукупності для об'єднання значень рядків?
a_horse_with_no_name

0

Я використовую Jetbrains Rider, і було клопотанням скопіювати результати з наведених вище прикладів, щоб повторно виконати, тому що, здавалося, все це завершено в JSON. Це приєднує їх до єдиного твердження, яке було легше запустити

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

0

Якщо ви перебуваєте на Amazon Redshift, де string_agg не підтримується, спробуйте використати listagg.

SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.