Я ищу способ объединить строки поля в группу по запросу. Так, например, у меня есть таблица:
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, ответ Деринга лучше только для 8.4 и ниже.
Этот вопрос больше подходит для dba.stackexchange.com.
Теперь это должен быть правильный ответ stackoverflow.com/a/47638417/243233


Я не претендую на ответ, потому что нашел его после некоторых поисков:
Чего я не знал, так это того, что 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;
Современный 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 (в 2009 году) представил агрегатная функция array_agg(expression), который собирает значения в массиве. Затем array_to_string() можно использовать для получения желаемого результата:
SELECT company_id, array_to_string(array_agg(employee), ', ')
FROM mytable
GROUP BY company_id;
Когда изначально был задан этот вопрос, не существовало встроенной агрегатной функции для объединения строк. Простейшая индивидуальная реализация (предложено Вайдой Габо в этом сообщении списка рассылки, среди многих других) заключается в использовании встроенной функции 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), но это здорово!
Вы можете написать функцию только на SQL, что проще для установки (plpgsql должен быть установлен суперпользователем). См. Мой пост для примера.
«Нет встроенной агрегатной функции для объединения строк» - почему бы вам не использовать array_to_string(array_agg(employee), ',')?
+1 для функции PostgreSQL 9.0. Если вам нужно беспокоиться о версии до 9.0, ответ Маркуса лучше.
Обратите внимание, что последние версии Postgres также позволяют использовать предложение Order By внутри агрегатной функции, например string_agg(employee, ',' Order By employee)
NB: вам может потребоваться преобразовать поле в текст: string_agg (something_id :: text, ',')
IBM Big SQL вроде как основан на Postgres, и вы можете добиться этого с помощью чего-то похожего на array_agg (который работает, но array_to_string нет). Вместо этого используйте listagg, например: listagg(DISTINCT employee, ', ').
@Neall Я только что понял, что сильно отредактировал твой ответ, потому что думал, что он мой! Ой. Надеюсь, вы не против, но так все в одном месте без особых комментариев.
@Neall вы также можете удалить повторяющиеся значения, используя разные. Следующий запрос объединит только разные имена сотрудников. ВЫБЕРИТЕ company_id, string_agg (отдельный сотрудник, ',') ИЗ mytable GROUP BY company_id;
Как уже упоминалось, создание собственной агрегатной функции - правильное решение. Вот моя агрегатная функция конкатенации (вы можете найти подробности на французском):
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:
Я бы дал ссылку на документацию по разработке 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 все равно
У меня отлично работает на Greenplum 4.3.4.1 (построено на PostgreSQL 8.2.15).
Начиная с 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
Как это связано с использованием агрегата для объединения строковых значений?
Я использую 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;
Ответ Маркуса Деринга технически лучше.