SQL GROUP BY y funciones de agregación: resume tus datos
Published: 2026-04-08
GROUP BY colapsa varias filas que comparten el mismo valor en una o más columnas en una sola fila de resumen. Combinado con funciones de agregación como COUNT, SUM, AVG, MIN y MAX, es la base de casi todas las consultas de informes y analítica. GROUP BY está disponible en todas las bases de datos SQL, incluidas SQLite, PostgreSQL, MySQL, SQL Server y Oracle.
Datos de ejemplo
Usamos dos tablas en todos los ejemplos:
Tabla employees:
| id | name | department | hire_date | salary |
|---|---|---|---|---|
| 1 | Alice | Engineering | 2023-01-15 | 95000 |
| 2 | Bob | Engineering | 2023-06-01 | 85000 |
| 3 | Carol | Engineering | 2024-03-10 | 85000 |
| 4 | Dave | Sales | 2023-02-20 | 72000 |
| 5 | Eve | Sales | 2023-09-05 | 68000 |
| 6 | Frank | Sales | 2024-01-15 | 65000 |
| 7 | Grace | Marketing | 2023-04-01 | 82000 |
| 8 | Henry | Marketing | 2024-06-01 | 70000 |
Tabla orders:
| id | customer | product | category | quantity | unit_price | order_date | status |
|---|---|---|---|---|---|---|---|
| 1 | Acme Corp | Laptop Pro | Electronics | 3 | 1299.99 | 2026-01-05 | delivered |
| 2 | Acme Corp | Wireless Mouse | Electronics | 10 | 29.99 | 2026-01-12 | delivered |
| 3 | Globex | Office Chair | Furniture | 5 | 249.99 | 2026-01-18 | delivered |
| 4 | Globex | Standing Desk | Furniture | 2 | 599.99 | 2026-01-20 | shipped |
| 5 | Initech | Notebook Pack | Stationery | 20 | 12.99 | 2026-02-03 | delivered |
| 6 | Initech | Pen Set | Stationery | 15 | 8.99 | 2026-02-07 | delivered |
| 7 | Acme Corp | Monitor 27" | Electronics | 4 | 399.99 | 2026-02-14 | delivered |
| 8 | Umbrella Ltd | Laptop Pro | Electronics | 2 | 1299.99 | 2026-02-20 | cancelled |
| 9 | Umbrella Ltd | Desk Lamp | Furniture | 8 | 39.99 | 2026-03-01 | delivered |
| 10 | Globex | Wireless Mouse | Electronics | 12 | 29.99 | 2026-03-05 | shipped |
| 11 | Initech | Monitor 27" | Electronics | 1 | 399.99 | 2026-03-10 | delivered |
| 12 | Acme Corp | Standing Desk | Furniture | 1 | 599.99 | 2026-03-15 | processing |
Sintaxis de GROUP BY
SELECT column1, aggregate_function(column2)
FROM table_name
WHERE condition
GROUP BY column1
HAVING aggregate_condition
ORDER BY column1;
- GROUP BY: una o más columnas cuyas combinaciones de valores distintos definen cada grupo
- Funciones de agregación: calculan un solo valor a partir de todas las filas del grupo
- HAVING: filtra grupos (como WHERE, pero para resultados agrupados)
- Cada columna en SELECT que no esté dentro de una agregación debe aparecer en GROUP BY
Orden de ejecución de SQL
Las cláusulas SQL se escriben en un orden pero se ejecutan en otro. Entender esto ayuda a explicar por qué HAVING puede hacer referencia a funciones de agregación pero WHERE no, y por qué no puedes usar un alias de SELECT en una cláusula HAVING.
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
| Paso | Cláusula | Qué sucede |
|---|---|---|
| 1 | FROM | Carga la(s) tabla(s) y evalúa cualquier JOIN |
| 2 | WHERE | Filtra filas individuales — las funciones de agregación no están permitidas aquí |
| 3 | GROUP BY | Colapsa las filas restantes en grupos |
| 4 | HAVING | Filtra grupos — las funciones de agregación sí están permitidas aquí |
| 5 | SELECT | Calcula las columnas de salida y los alias |
| 6 | ORDER BY | Ordena el conjunto de resultados final |
Por eso WHERE status = 'delivered' se ejecuta antes del agrupamiento (rápido, filtro a nivel de fila), mientras que HAVING COUNT(*) > 2 se ejecuta después del agrupamiento (filtra grupos completos).
COUNT()
COUNT(*) cuenta todas las filas de un grupo. COUNT(column) cuenta los valores no NULL de esa columna.
Ejemplo: número de empleados por departamento
Ejemplo: número de pedidos por cliente
Ejemplo: contar entregados frente a otros estados
Cuenta solo los pedidos que coinciden con una condición usando COUNT con una expresión CASE:
SUM()
SUM(column) suma todos los valores del grupo para una columna numérica.
Ejemplo: salario total por departamento
Ejemplo: ingresos totales por categoría
Calcula los ingresos como quantity * unit_price:
Ejemplo: ingresos por cliente y categoría
Agrupa por varias columnas para desglosar más los ingresos:
AVG()
AVG(column) devuelve la media aritmética de todos los valores no NULL del grupo.
Ejemplo: salario promedio por departamento
Ejemplo: valor promedio de pedido por cliente
MIN() y MAX()
MIN(column) y MAX(column) devuelven los valores más pequeño y más grande del grupo.
Ejemplo: rango salarial por departamento
Ejemplo: primera y última fecha de pedido por cliente
Combinando múltiples agregaciones
Puedes combinar cualquier número de funciones de agregación en una sola consulta.
Ejemplo: resumen completo por departamento
Ejemplo: resumen de pedidos por cliente
HAVING
HAVING filtra grupos después de la agregación: es el equivalente de WHERE para resultados agrupados.
Ejemplo: departamentos con más de dos empleados
Ejemplo: clientes con ingresos totales superiores a 2000
Ejemplo: categorías con valor promedio de pedido inferior a 500
WHERE vs. HAVING
WHERE filtra filas individuales antes del agrupamiento. HAVING filtra grupos después de la agregación.
Ejemplo: ingresos solo de pedidos entregados por cliente
Usa WHERE para excluir filas antes de agrupar y luego HAVING para filtrar resultados:
GROUP BY con JOINs
Puedes agrupar datos que abarcan varias tablas uniéndolas primero.
Ejemplo: ingresos por departamento mediante JOIN con empleados
Ejemplo: número de pedidos e ingresos por categoría y mes
GROUP BY con expresiones
Puedes agrupar por expresiones calculadas, no solo por columnas simples.
Ejemplo: empleados contratados por año
Ejemplo: pedidos por mes
COUNT(DISTINCT)
COUNT(DISTINCT column) cuenta el número de valores únicos del grupo.
Ejemplo: productos únicos pedidos por cliente
Filtrar grupos con múltiples condiciones
HAVING admite cualquier expresión booleana, incluidas combinaciones AND/OR.
Ejemplo: clientes activos de alto valor
Encuentra clientes con más de un pedido e ingresos totales superiores a 1000:
Ejemplo práctico: informe de ventas
Combina todo en un informe completo de resumen de ventas.
Ejemplo: informe de rendimiento por categoría
Ejemplo: producto principal por ingresos
GROUP BY vs. funciones de ventana
GROUP BY colapsa filas en una fila por grupo. Las funciones de ventana mantienen todas las filas y añaden una columna calculada; consulta el tutorial de funciones de ventana en SQL para más detalles.
Ejemplo: GROUP BY devuelve una fila por departamento
Ejemplo: una función de ventana mantiene todas las filas
Casos de uso comunes de GROUP BY
- Paneles de informes: resume totales, promedios y conteos por categoría o periodo de tiempo
- Análisis de dotación: cuenta empleados, usuarios o registros por grupo
- Desglose de ingresos: ingresos totales y promedio por producto, región o cliente
- Análisis de tendencias: agrupa por mes o año para detectar patrones a lo largo del tiempo
- Top-N por grupo: combina con subconsultas o CTE para encontrar el mejor desempeño en cada grupo
- Validación de datos: cuenta duplicados o valores NULL en los grupos
- Análisis de cohortes: agrupa usuarios por fecha de registro o segmento de comportamiento
Consejos de rendimiento
- Indexa las columnas de GROUP BY: los índices en las columnas agrupadas aceleran el paso de ordenación y agrupamiento
- Filtra pronto con WHERE: reducir filas antes de agrupar con WHERE es más rápido que filtrar después con HAVING
- Evita SELECT *: selecciona solo las columnas que realmente necesitas; las operaciones de agregación se benefician de datos más estrechos
- Usa índices cubrientes: un índice que incluya tanto la columna de GROUP BY como la columna agregada puede evitar un escaneo completo de la tabla
- Considera vistas materializadas: para agregados calculados con frecuencia en tablas grandes, algunas bases de datos admiten resúmenes precomputados
Resumen
- GROUP BY colapsa filas que comparten los mismos valores de columna en una fila de resumen por combinación única
- COUNT(*) cuenta todas las filas; COUNT(column) cuenta valores no NULL; COUNT(DISTINCT column) cuenta valores únicos
- SUM suma valores numéricos; AVG calcula la media; MIN / MAX encuentran los límites
- Cada columna de SELECT debe aparecer en GROUP BY o estar envuelta en una función de agregación
- WHERE filtra filas antes del agrupamiento; HAVING filtra grupos después de la agregación
- Puedes usar GROUP BY con expresiones (por ejemplo,
SUBSTR(date, 1, 7)para agrupar a nivel de mes) - Unir tablas antes de agrupar te permite combinar datos de múltiples fuentes en un solo informe
- Usa funciones de ventana en lugar de GROUP BY cuando necesites métricas a nivel de grupo sin perder filas individuales
Prueba a combinar GROUP BY con JOINs, expresiones CASE y cláusulas HAVING para crear los informes que tu análisis de datos necesita.