Como concatenar strings de um campo de string em uma consulta do 'grupo por' do PostgreSQL?

351

Eu estou procurando uma maneira de concatenar as seqüências de caracteres de um campo dentro de um grupo por consulta. Então, por exemplo, eu tenho uma tabela:

ID   COMPANY_ID   EMPLOYEE
1    1            Anna
2    1            Bill
3    2            Carol
4    2            Dave

e eu queria agrupar por company_id para obter algo como:

COMPANY_ID   EMPLOYEE
1            Anna, Bill
2            Carol, Dave

Há uma função interna no mySQL para fazer isso group_concat

Guy C
fonte
11
A resposta de Markus Döring é tecnicamente melhor.
pstanton 01/09/11
@pstanton, a resposta de Döring é melhor apenas para 8.4 e abaixo.
Jared Beck
Esta pergunta parece ser mais adequada para dba.stackexchange.com .
21414 Dave
Esta deve ser a resposta válida agora stackoverflow.com/a/47638417/243233
Jus12

Respostas:

542

PostgreSQL 9.0 ou posterior:

Versões recentes do Postgres (desde o final de 2010) têm a string_agg(expression, delimiter)função que fará exatamente o que a pergunta pediu, até mesmo permitindo que você especifique a string delimitadora:

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

O Postgres 9.0 também adicionou a capacidade de especificar uma ORDER BYcláusula em qualquer expressão agregada ; caso contrário, o pedido não será definido. Agora você pode escrever:

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

Ou de fato:

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

PostgreSQL 8.4 ou posterior:

O PostgreSQL 8.4 (em 2009) introduziu a função agregadaarray_agg(expression) que concatena os valores em uma matriz. Então array_to_string()pode ser usado para dar o resultado desejado:

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

string_agg para versões anteriores à 8.4:

Caso alguém se depare com isso procurando uma correção de compatibilidade para bancos de dados anteriores à 9.0, é possível implementar tudo, string_aggexceto a ORDER BYcláusula.

Portanto, com a definição abaixo, isso deve funcionar da mesma maneira que no DB 9.x do Postgres:

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

Mas este será um erro de sintaxe:

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

Testado no 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
);

Variações personalizadas (todas as versões do Postgres)

Antes da 9.0, não havia função agregada incorporada para concatenar seqüências de caracteres. A implementação personalizada mais simples ( sugerida por Vajda Gabo nesta postagem da lista de discussão , entre muitas outras) é usar a textcatfunção interna (que fica atrás do ||operador):

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

Aqui está a CREATE AGGREGATEdocumentação.

Isso simplesmente cola todas as cordas, sem separador. Para obter um "," inserido entre eles sem tê-lo no final, convém criar sua própria função de concatenação e substituí-la pelo "textcat" acima. Aqui está um que eu montei e testei em 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;

Esta versão produzirá uma vírgula mesmo que o valor na linha seja nulo ou vazio, para que você obtenha uma saída como esta:

a, b, c, , e, , g

Se você preferir remover vírgulas extras para gerar isso:

a, b, c, e, g

Em seguida, adicione uma ELSIFverificação à função como esta:

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;
Neall
fonte
11
Eu tive que S&R varchar para texto (mais recente pgsql estável), mas isso é ótimo!
Kev
11
Você pode gravar a função apenas no SQL, o que é mais fácil para a instalação (o plpgsql precisa ser instalado pelo superusuário). Veja minha postagem para um exemplo.
22448 bortzmeyer
11
"Não há função agregada incorporada para concatenar seqüências de caracteres" - por que você não usaria array_to_string(array_agg(employee), ',')?
pstanton 01/09/11
2
+1 para a função PostgreSQL 9.0. Se você precisa se preocupar com o pré-9.0, a resposta de Markus é melhor.
Brad Koch
7
Note-se que versões mais recentes do Postgres também permitir uma Order Bycláusula dentro da função de agregação, por exemplo,string_agg(employee, ',' Order By employee)
IMSOP
99

Que tal usar as funções de matriz incorporadas do Postgres? Pelo menos no 8.4, isso funciona imediatamente:

SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
Markus Döring
fonte
infelizmente isso não funciona para nós no Greenplum (v8.2). +1 tudo igual
ekkis 25/08
Funciona bem para mim no Greenplum 4.3.4.1 (construído no PostgreSQL 8.2.15).
PhilHibbs
19

A partir do PostgreSQL 9.0, você pode usar a função agregada chamada string_agg . Seu novo SQL deve ser algo como isto:

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

dirbacke
fonte
13

Não reivindico crédito pela resposta porque a encontrei após algumas pesquisas:

O que eu não sabia é que o PostgreSQL permite que você defina suas próprias funções agregadas com CREATE AGGREGATE

Este post na lista do PostgreSQL mostra como é trivial criar uma função para fazer o que é necessário:

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

SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;
Guy C
fonte
7

Como já mencionado, criar sua própria função agregada é a coisa certa a fazer. Aqui está minha função agregada de concatenação (você pode encontrar detalhes em francês ):

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 = ''

);

E depois use-o como:

SELECT company_id, concatenate(employee) AS employees FROM ...
bortzmeyer
fonte
5

Este último snippet da lista de anúncios pode ser interessante se você atualizar para a 8.4:

Até que o 8.4 seja lançado com um nativo super eficiente, você pode adicionar a função array_accum () na documentação do PostgreSQL para acumular qualquer coluna em um array, que pode ser usado pelo código do aplicativo ou combinado com o array_to_string () para formatar como uma lista:

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

Eu vincularia aos documentos de desenvolvimento 8.4, mas eles ainda não parecem listar esse recurso.

Kev
fonte
5

Seguindo a resposta de Kev, usando os documentos do Postgres:

Primeiro, crie uma matriz dos elementos e use a array_to_stringfunção interna.

CREATE AGGREGATE array_accum (anyelement)
(
 sfunc = array_append,
 stype = anyarray,
 initcond = '{}'
);

select array_to_string(array_accum(name),'|') from table group by id;
Brad Koch
fonte
5

Seguindo mais uma vez o uso de uma função agregada personalizada da concatenação de cadeias: você precisa lembrar que a instrução select colocará linhas em qualquer ordem, portanto, será necessário fazer uma sub- seleção na instrução from com uma cláusula order by , e em seguida, uma seleção externa com uma cláusula group by para agregar as strings, assim:

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
Brad Koch
fonte
2

Use a STRING_AGGfunção para PostgreSQL e Google BigQuery SQL :

SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
Valentin Podkamennyi
fonte
0

De acordo com a versão PostgreSQL 9.0 e superior, você pode usar a função agregada chamada string_agg. Seu novo SQL deve ser algo como isto:

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

Você também pode usar a função de formatação. O que também pode implicitamente cuidar da conversão de tipo de texto, int, etc. por si só.

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
Sandip Debnath
fonte
11
Como isso está relacionado ao uso de um agregado para concatenar valores de string?
precisa saber é o seguinte
0

Estou usando o Jetbrains Rider e foi um incômodo copiar os resultados dos exemplos acima para executar novamente, porque parecia envolver tudo isso em JSON. Isso os une em uma única instrução que era mais fácil de executar

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

Se você estiver no Amazon Redshift, onde string_agg não é suportado, tente usar listagg.

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