¿Cómo concatenar cadenas de un campo de cadena en una consulta 'agrupar por' de PostgreSQL?
-
09-06-2019 - |
Pregunta
Estoy buscando una manera de concatenar las cadenas de un campo dentro de un grupo mediante consulta.Entonces, por ejemplo, tengo una tabla:
ID COMPANY_ID EMPLOYEE
1 1 Anna
2 1 Bill
3 2 Carol
4 2 Dave
y quería agrupar por company_id para obtener algo como:
COMPANY_ID EMPLOYEE
1 Anna, Bill
2 Carol, Dave
Hay una función incorporada en MySQL para hacer esto. grupo_concat
Solución
PostgreSQL 9.0 o posterior:
Las versiones recientes de Postgres (desde finales de 2010) tienen la string_agg(expression, delimiter)
función que hará exactamente lo que pedía la pregunta, incluso permitiéndole especificar la cadena delimitadora:
SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;
Postgres 9.0 también agregó la capacidad de especificar un ORDER BY
cláusula en cualquier expresión agregada;de lo contrario, el orden no está definido.Entonces ahora puedes escribir:
SELECT company_id, string_agg(employee, ', ' ORDER BY employee)
FROM mytable
GROUP BY company_id;
O de hecho:
SELECT string_agg(actor_name, ', ' ORDER BY first_appearance)
PostgreSQL 8.4 o posterior:
PostgreSQL 8.4 (en 2009) introducido la función agregada array_agg(expression)
que concatena los valores en una matriz.Entonces array_to_string()
se puede utilizar para dar el resultado deseado:
SELECT company_id, array_to_string(array_agg(employee), ', ')
FROM mytable
GROUP BY company_id;
string_agg
para versiones anteriores a 9.0:
En caso de que alguien se encuentre con esto buscando una corrección de compatibilidad para bases de datos anteriores a 9.0, es posible implementar todo en string_agg
excepto el ORDER BY
cláusula.
Entonces, con la siguiente definición, esto debería funcionar igual que en una base de datos Postgres 9.x:
SELECT string_agg(name, '; ') AS semi_colon_separated_names FROM things;
Pero esto será un error de sintaxis:
SELECT string_agg(name, '; ' ORDER BY name) AS semi_colon_separated_names FROM things;
--> ERROR: syntax error at or near "ORDER"
Probado en 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
);
Variaciones personalizadas (todas las versiones de Postgres)
Antes de la versión 9.0, no había una función agregada incorporada para concatenar cadenas.La implementación personalizada más simple (sugerido por Vajda Gabo en esta publicación de la lista de correo, entre muchos otros) es utilizar el integrado textcat
función (que se encuentra detrás de la ||
operador):
CREATE AGGREGATE textcat_all(
basetype = text,
sfunc = textcat,
stype = text,
initcond = ''
);
Aquí está el CREATE AGGREGATE
documentación.
Esto simplemente pega todas las cuerdas juntas, sin separador.Para poder insertar un "," entre ellos sin tenerlo al final, es posible que desee crear su propia función de concatenación y sustituirla por el "textcat" anterior.Aquí hay uno que armé y probé en 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 versión generará una coma incluso si el valor de la fila es nulo o está vacío, por lo que obtendrá un resultado como este:
a, b, c, , e, , g
Si prefiere eliminar comas adicionales para generar esto:
a, b, c, e, g
Luego agrega un ELSIF
verifique la función 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;
Otros consejos
¿Qué tal si utilizamos las funciones de matriz integradas de Postgres?Al menos en 8.4, esto funciona de inmediato:
SELECT company_id, array_to_string(array_agg(employee), ',')
FROM mytable
GROUP BY company_id;
A partir de PostgreSQL 9.0 puedes utilizar la función agregada llamada cadena_agg.Su nuevo SQL debería verse así:
SELECT company_id, string_agg(employee, ', ')
FROM mytable
GROUP BY company_id;
No reclamo ningún crédito por la respuesta porque la encontré después de buscar un poco:
Lo que no sabía es que PostgreSQL te permite definir tus propias funciones agregadas con CREAR AGREGADO
Esta publicación en la lista de PostgreSQL muestra lo trivial que es crear una función para hacer lo que se requiere:
CREATE AGGREGATE textcat_all(
basetype = text,
sfunc = textcat,
stype = text,
initcond = ''
);
SELECT company_id, textcat_all(employee || ', ')
FROM mytable
GROUP BY company_id;
Como ya se mencionó, crear su propia función agregada es lo correcto.Aquí está mi función agregada de concatenación (puedes encontrar detalles en 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 = ''
);
Y luego úsalo como:
SELECT company_id, concatenate(employee) AS employees FROM ...
Este último fragmento de la lista de anuncios puede ser de interés si actualiza a 8.4:
Hasta que 8.4 salga con uno nativo de Super-Effient, puede agregar la función Array_Accum () en la documentación PostgreSQL para encender cualquier columna en una matriz, que luego se puede usar mediante código de aplicación, o combinado con Array_TO_String () para formatear es como una lista:
Me vincularía a los documentos de desarrollo 8.4, pero parece que todavía no incluyen esta característica.
Siguiendo la respuesta de Kev, utilizando los documentos de Postgres:
Primero, cree una matriz de elementos, luego use el incorporado array_to_string
función.
CREATE AGGREGATE array_accum (anyelement)
(
sfunc = array_append,
stype = anyarray,
initcond = '{}'
);
select array_to_string(array_accum(name),'|') from table group by id;
Siguiendo una vez más sobre el uso de una función agregada personalizada de concatenación de cadenas:debe recordar que la instrucción de selección colocará filas en cualquier orden, por lo que deberá hacer una sub seleccionar en el de declaración con un ordenar por cláusula, y luego una exterior seleccionar con un agrupar por cláusula para agregar las cadenas, así:
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
Encontré útil esta documentación de PostgreSQL: http://www.postgresql.org/docs/8.0/interactive/functions-conditional.html.
En mi caso, busqué SQL simple para concatenar un campo con corchetes alrededor, si el campo no está vacío.
select itemid,
CASE
itemdescription WHEN '' THEN itemname
ELSE itemname || ' (' || itemdescription || ')'
END
from items;
Usar STRING_AGG
función para PostgreSQL y SQL de BigQuery de Google:
SELECT company_id, STRING_AGG(employee, ', ')
FROM employees
GROUP BY company_id;
Según la versión PostgreSQL 9.0 y superior, puede utilizar la función agregada llamada string_agg.Su nuevo SQL debería verse así:
SELECT company_id, string_agg(employee, ', ')
FROM mytable GROUP BY company_id;
También puede utilizar la función de formato.Lo cual también puede encargarse implícitamente de la conversión de tipos de texto, int, etc. por sí solo.
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
Estoy usando Jetbrains Rider y fue una molestia copiar los resultados de los ejemplos anteriores para volver a ejecutarlos porque parecía envolverlo todo en JSON.Esto los une en una sola declaración que fue más fácil de ejecutar.
select string_agg('drop table if exists "' || tablename || '" cascade', ';')
from pg_tables where schemaname != $$pg_catalog$$ and tableName like $$rm_%$$
Si está en Amazon Redshift, donde no se admite string_agg, intente usar listagg.
SELECT company_id, listagg(EMPLOYEE, ', ') as employees
FROM EMPLOYEE_table
GROUP BY company_id;