GROUP_CONCAT

GROUP_CONCAT es una de las funciones de agregación más versátiles y populares de MySQL. A diferencia de SUM o COUNT que devuelven un número, GROUP_CONCAT devuelve una cadena de texto que concatena todos los valores de un grupo, separados por coma u otro delimitador que tú elijas. Es perfecta para crear listas de etiquetas, enumerar productos de un pedido, listar categorías de un artículo o cualquier situación donde necesites combinar múltiples valores en una sola celda del resultado.

Sintaxis

GROUP_CONCAT(
    [DISTINCT] expresion [, expresion2, ...]
    [ORDER BY columna [ASC | DESC]]
    [SEPARATOR 'cadena']
)

La sintaxis completa permite eliminar duplicados con DISTINCT, ordenar los valores dentro de la cadena con ORDER BY y cambiar el separador con SEPARATOR. El separador por defecto es la coma (,). Cada una de estas opciones es opcional y se pueden combinar entre sí.

Comportamiento básico

Veamos el caso más simple: listar todos los nombres de productos de cada categoría en una sola cadena:

SELECT
    c.nombre AS categoria,
    GROUP_CONCAT(p.nombre) AS productos
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
WHERE c.nombre = 'Libros'
GROUP BY c.nombre;
categoriaproductos
LibrosEl Quijote edición bolsillo,Cien años de soledad tapa dura

Los dos libros aparecen separados por coma en una sola celda. Sin GROUP_CONCAT, necesitarías dos filas para mostrar esta información, lo que en muchos contextos (informes, APIs, exportaciones) resulta menos práctico.

Caso práctico: personalizar el separador

El separador por defecto es la coma, pero puedes cambiarlo por cualquier cadena con la cláusula SEPARATOR:

SELECT
    c.nombre AS categoria,
    GROUP_CONCAT(p.nombre SEPARATOR ' | ') AS productos
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
ORDER BY c.nombre;
categoriaproductos
Accesorios electrónicosCable USB-C a Lightning | Cargador USB-C 65W | Funda iPhone silicona | ...
DeportesBanda de resistencia | Esterilla yoga premium | Mancuernas ajustables | ...
HogarVela aromática lavanda | Juego de sábanas algodón | Lámpara escritorio LED | ...

El separador | hace la lista más legible. Puedes usar cualquier cadena: ' - ', ', ' (coma con espacio), '; ', '\n' (salto de línea) o incluso etiquetas HTML como '<br>' si el resultado va a una página web.

Caso práctico: ORDER BY dentro de GROUP_CONCAT

Puedes controlar el orden en que aparecen los valores dentro de la cadena:

SELECT
    c.nombre AS categoria,
    GROUP_CONCAT(p.nombre ORDER BY p.precio DESC SEPARATOR ', ') AS productos_por_precio
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
WHERE c.nombre = 'Smartphones'
GROUP BY c.nombre;
categoriaproductos_por_precio
SmartphonesiPhone 15 Pro, Samsung Galaxy S24 Ultra, Google Pixel 8 Pro, Samsung Galaxy S24, Xiaomi 14, Google Pixel 8, OnePlus 12, ...

Los smartphones aparecen ordenados de mayor a menor precio. El ORDER BY dentro de GROUP_CONCAT es independiente del ORDER BY global de la consulta: uno ordena los valores dentro de cada cadena y el otro ordena las filas del resultado.

Caso práctico: DISTINCT para eliminar duplicados

Si los datos contienen duplicados, puedes eliminarlos con DISTINCT:

SELECT
    cl.nombre,
    cl.apellidos,
    GROUP_CONCAT(DISTINCT c.nombre ORDER BY c.nombre SEPARATOR ', ') AS categorias_compradas
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
JOIN productos pr ON dp.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
GROUP BY cl.id, cl.nombre, cl.apellidos
ORDER BY cl.nombre
LIMIT 5;
nombreapellidoscategorias_compradas
AnaMartínez RuizAccesorios electrónicos, Hogar, Portátiles, Smartphones
CarlosRodríguez MartínAccesorios electrónicos, Deportes, Portátiles, Ropa hombre, Smartphones
LauraFernández DíazAccesorios electrónicos, Hogar, Ropa mujer, Smartphones
MaríaGarcía LópezAccesorios electrónicos, Deportes, Portátiles, Ropa mujer, Smartphones
PedroSánchez GómezAccesorios electrónicos, Portátiles, Smartphones

Sin DISTINCT, si María compró tres veces en la categoría Smartphones, esa categoría aparecería tres veces en la lista. Con DISTINCT, cada categoría aparece una sola vez.

Caso práctico: concatenar expresiones complejas

GROUP_CONCAT no está limitado a una sola columna. Puedes concatenar expresiones que combinen varias columnas:

SELECT
    p.id AS pedido_id,
    p.fecha_pedido,
    GROUP_CONCAT(
        CONCAT(pr.nombre, ' (x', dp.cantidad, ')')
        ORDER BY pr.nombre
        SEPARATOR ', '
    ) AS detalle
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
JOIN productos pr ON dp.producto_id = pr.id
WHERE p.id IN (142, 89, 215)
GROUP BY p.id, p.fecha_pedido
ORDER BY p.id;
pedido_idfecha_pedidodetalle
892025-04-12Cargador USB-C 65W (x2), MacBook Air M3 (x1), Samsung Galaxy S24 (x1)
1422025-07-20Funda iPhone silicona (x3), iPhone 15 Pro (x1), Lenovo ThinkPad X1 (x1)
2152025-10-05ASUS ROG Zephyrus (x1), Cable USB-C a Lightning (x2), Google Pixel 8 (x1)

Cada pedido muestra su contenido completo en una sola línea, incluyendo la cantidad entre paréntesis. Esto es extremadamente útil para informes y vistas resumidas.

Caso práctico: crear listas para consultas IN

Un uso práctico es generar listas que puedes copiar y usar en otras consultas:

SELECT
    GROUP_CONCAT(id SEPARATOR ', ') AS ids_premium
FROM productos
WHERE precio > 1000;
ids_premium
1, 5, 6, 7, 15, 22, 34, 41

Este resultado puedes copiarlo directamente en una cláusula WHERE id IN (...) de otra consulta. Es una técnica muy útil durante el desarrollo y la depuración.

Caso práctico: etiquetas y sistemas de tags

GROUP_CONCAT es la función natural para sistemas de etiquetas. Si tienes una tabla de relación entre productos y etiquetas:

SELECT
    p.nombre AS producto,
    GROUP_CONCAT(e.nombre ORDER BY e.nombre SEPARATOR ', ') AS etiquetas
FROM productos p
JOIN producto_etiquetas pe ON p.id = pe.producto_id
JOIN etiquetas e ON pe.etiqueta_id = e.id
GROUP BY p.id, p.nombre
ORDER BY p.nombre
LIMIT 5;
productoetiquetas
ASUS ROG Zephyrusgaming, pantalla OLED, portátil, premium
Cable USB-C a Lightningaccesorio, Apple, cable, económico
Cargador USB-C 65Waccesorio, carga rápida, universal
Funda iPhone siliconaaccesorio, Apple, protección
Google Pixel 8Android, cámara, Google, smartphone

Manejo de NULL

GROUP_CONCAT ignora los valores NULL en la concatenación. No los incluye en la cadena resultado ni añade separadores vacíos por ellos:

SELECT
    GROUP_CONCAT(telefono SEPARATOR ', ') AS telefonos
FROM empleados
WHERE departamento_id = 1;

Si tres de los cinco empleados del departamento tienen teléfono y los otros dos tienen NULL, el resultado contendrá solo tres números separados por comas, sin comas vacías ni la palabra "NULL".

Si quieres incluir un texto en lugar de los NULL, usa IFNULL:

SELECT
    GROUP_CONCAT(
        IFNULL(telefono, 'Sin teléfono')
        SEPARATOR ', '
    ) AS telefonos
FROM empleados
WHERE departamento_id = 1;

La variable group_concat_max_len

Un aspecto crítico de GROUP_CONCAT es que tiene un límite de longitud máxima controlado por la variable de sistema group_concat_max_len. El valor predeterminado es 1024 bytes, lo que puede ser insuficiente si concatenas muchos valores largos.

Para ver el valor actual:

SHOW VARIABLES LIKE 'group_concat_max_len';
Variable_nameValue
group_concat_max_len1024

Si el resultado de GROUP_CONCAT supera este límite, se trunca silenciosamente sin ningún error. Esto puede causar resultados incorrectos sin que te des cuenta. Para aumentar el límite:

SET SESSION group_concat_max_len = 100000;

Puedes establecerlo a nivel de sesión (como arriba) o a nivel global para que afecte a todas las conexiones nuevas:

SET GLOBAL group_concat_max_len = 100000;

Si tu aplicación depende de GROUP_CONCAT con muchos valores, siempre establece este valor explícitamente al inicio de la conexión.

Combinación con otras funciones

GROUP_CONCAT se combina bien con HAVING para filtrar grupos según la cadena resultante o según otras métricas:

SELECT
    cl.nombre,
    cl.apellidos,
    COUNT(DISTINCT c.nombre) AS num_categorias,
    GROUP_CONCAT(DISTINCT c.nombre ORDER BY c.nombre SEPARATOR ', ') AS categorias
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
JOIN productos pr ON dp.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
GROUP BY cl.id, cl.nombre, cl.apellidos
HAVING COUNT(DISTINCT c.nombre) >= 4
ORDER BY num_categorias DESC;
nombreapellidosnum_categoriascategorias
CarlosRodríguez Martín5Accesorios electrónicos, Deportes, Portátiles, Ropa hombre, Smartphones
MaríaGarcía López5Accesorios electrónicos, Deportes, Portátiles, Ropa mujer, Smartphones
AnaMartínez Ruiz4Accesorios electrónicos, Hogar, Portátiles, Smartphones
LauraFernández Díaz4Accesorios electrónicos, Hogar, Ropa mujer, Smartphones

Esta consulta identifica a los clientes más diversificados: aquellos que compran en 4 o más categorías diferentes. La combinación de COUNT(DISTINCT) para filtrar con GROUP_CONCAT para mostrar la lista es un patrón muy frecuente.

También puedes usar GROUP_CONCAT en subconsultas para enriquecer cada fila con una lista:

SELECT
    c.nombre AS categoria,
    COUNT(*) AS total_productos,
    (SELECT GROUP_CONCAT(p2.nombre ORDER BY p2.precio DESC SEPARATOR ', ')
     FROM productos p2
     WHERE p2.categoria_id = c.id AND p2.precio > 500
    ) AS productos_premium
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.id, c.nombre
ORDER BY total_productos DESC;

Este enfoque pone la lista de productos premium directamente al lado de cada categoría, sin necesidad de agrupar la consulta principal por ella.

Practica con GROUP_CONCAT

Usa el editor para concatenar valores agrupados:

Simulador SQL
Ctrl+Enter para ejecutar

En el siguiente artículo veremos STD para calcular la desviación estándar.

Escrito por Eduardo Lázaro