
Problema que resuelven
Imagina que necesitas un informe de ventas con total por producto, total por categoría y total general en una sola salida. Sin extensiones estándar, habría que hacer tres consultas y unirlas con UNION ALL:
-- Solucion sin extensiones: 3 queries + UNION
SELECT categoria, producto, SUM(ventas) FROM pedidos GROUP BY categoria, producto
UNION ALL
SELECT categoria, NULL, SUM(ventas) FROM pedidos GROUP BY categoria
UNION ALL
SELECT NULL, NULL, SUM(ventas) FROM pedidos;
Las extensiones GROUPING SETS, ROLLUP y CUBE (estándar SQL:1999, soportadas por PostgreSQL, SQL Server, Oracle, MySQL 8+) simplifican esto a una sola consulta, además de aprovechar una sola pasada sobre los datos.
GROUPING SETS: combinaciones explícitas
GROUPING SETS permite listar exactamente qué combinaciones de columnas quieres agregar:
SELECT categoria, producto, SUM(ventas) AS total
FROM pedidos
GROUP BY GROUPING SETS (
(categoria, producto), -- detalle por producto
(categoria), -- subtotal por categoria
() -- total general
)
ORDER BY categoria NULLS LAST, producto NULLS LAST;
Salida con subtotales:
categoria | producto | total
------------+----------+-------
Electronica| Laptop | 5000
Electronica| Tablet | 3000
Electronica| NULL | 8000 <- subtotal categoria
Ropa | Camiseta | 1500
Ropa | NULL | 1500 <- subtotal categoria
NULL | NULL | 9500 <- total general
ROLLUP: subtotales jerárquicos
ROLLUP(a, b, c) es azúcar sintáctico equivalente a:
GROUPING SETS ((a, b, c), (a, b), (a), ())
Es decir, produce los agregados quitando columnas de derecha a izquierda. Ideal para jerarquías naturales como año-mes-día o país-región-ciudad:
SELECT anio, mes, dia, SUM(ingresos) AS total
FROM operaciones
GROUP BY ROLLUP (anio, mes, dia)
ORDER BY anio, mes, dia;
Produce: detalle por día, subtotal por mes (dia=NULL), subtotal por año (mes=NULL, dia=NULL), total general (todos NULL).
ROLLUP múltiple
También puedes combinar varios ROLLUP:
-- Por region y por periodo independientemente
SELECT pais, region, anio, mes, SUM(ventas)
FROM pedidos
GROUP BY ROLLUP (pais, region), ROLLUP (anio, mes);
CUBE: todas las combinaciones
CUBE(a, b, c) genera todas las combinaciones posibles de presencia/ausencia de las columnas (2^n grupos). Para 3 columnas son 8 grupos:
SELECT categoria, region, canal, SUM(ventas) AS total
FROM pedidos
GROUP BY CUBE (categoria, region, canal);
Los grupos generados son:
- (categoria, region, canal)
- (categoria, region)
- (categoria, canal)
- (categoria)
- (region, canal)
- (region)
- (canal)
- () total general
CUBE es útil para dashboards OLAP donde el usuario puede cruzar cualquier combinación de dimensiones. Cuidado: el coste crece exponencialmente con el número de columnas; no abuses.
La función GROUPING: distinguir NULL de NULL
Un problema habitual: en el resultado aparece NULL tanto porque la columna se ha agregado (subtotal) como porque el dato original era NULL. Para distinguirlos, la función GROUPING(col) devuelve 1 si la columna está agrupada (agregada) y 0 si su valor es el real:
SELECT
CASE WHEN GROUPING(categoria) = 1 THEN 'TOTAL' ELSE categoria END AS categoria,
CASE WHEN GROUPING(producto) = 1 THEN 'SUBTOTAL' ELSE producto END AS producto,
SUM(ventas) AS total
FROM pedidos
GROUP BY ROLLUP (categoria, producto);
Resultado legible sin ambigüedad de NULLs:
categoria | producto | total
------------+----------+-------
Electronica| Laptop | 5000
Electronica| Tablet | 3000
Electronica| SUBTOTAL | 8000
Ropa | Camiseta | 1500
Ropa | SUBTOTAL | 1500
TOTAL | SUBTOTAL | 9500
También existe GROUPING_ID(c1, c2, ...) que devuelve un bitmap con los niveles activos de agregación. Útil para ordenar jerarquías complejas.
Caso real: reporte financiero mensual
Un reporte típico de P&L agrupado por centro de coste, subcategoría y cuenta, con subtotales a todos los niveles:
SELECT
CASE WHEN GROUPING(centro) = 1 THEN 'TOTAL EMPRESA' ELSE centro END AS centro,
CASE WHEN GROUPING(categoria) = 1 THEN 'TOTAL CENTRO' ELSE categoria END AS categoria,
CASE WHEN GROUPING(cuenta) = 1 THEN 'TOTAL CATEGORIA' ELSE cuenta END AS cuenta,
SUM(importe) AS importe
FROM movimientos_contables
WHERE anio = 2026 AND mes = 4
GROUP BY ROLLUP (centro, categoria, cuenta)
ORDER BY GROUPING(centro), centro, GROUPING(categoria), categoria, GROUPING(cuenta), cuenta;
El truco del ORDER BY GROUPING(...) asegura que los subtotales aparezcan debajo de los detalles en lugar de mezclados (las filas agregadas tienen GROUPING=1, las detalladas GROUPING=0, así se ordenan primero los detalles y luego los subtotales).
Rendimiento
Estas extensiones suelen ser más rápidas que un UNION ALL de varias queries porque el motor puede:
- Hacer una única pasada sobre los datos.
- Reutilizar estructuras hash o de sort entre los distintos niveles de agregación.
En PostgreSQL el planner elige entre GroupAggregate (cuando los datos ya están ordenados) y HashAggregate (cuando no). Verifica con EXPLAIN ANALYZE:
EXPLAIN (ANALYZE, BUFFERS)
SELECT categoria, producto, SUM(ventas)
FROM pedidos
GROUP BY ROLLUP (categoria, producto);
Limitaciones y diferencias entre motores
| Motor | GROUPING SETS | ROLLUP | CUBE | GROUPING() |
|-------|---------------|--------|------|------------|
| PostgreSQL 9.5+ | ✓ | ✓ | ✓ | ✓ |
| SQL Server | ✓ | ✓ | ✓ | ✓ |
| Oracle | ✓ | ✓ | ✓ | ✓ |
| MySQL 8.0+ | ✗ | WITH ROLLUP (sintaxis vieja) | ✗ | ✓ |
En MySQL 8 se usa la sintaxis legacy GROUP BY col1, col2 WITH ROLLUP que solo equivale a ROLLUP, no soporta GROUPING SETS ni CUBE.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, SQL es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de SQL
Explora más contenido relacionado con SQL y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Usar GROUPING SETS para combinar en una consulta varios niveles de agregacion. Aplicar ROLLUP para generar subtotales jerarquicos (ano-mes-dia, pais-region-ciudad). Aplicar CUBE para todas las combinaciones posibles. Usar la funcion GROUPING(col) para distinguir NULL reales de NULL de agregacion. Combinar ROLLUP con ORDER BY para informes ordenados correctamente.