Tema 10: Funciones de agregación y GROUP BY: el poder de resumir
Identificador: PF-010 | XP: 200 | Insignia: 🥇 Oro | Tier: Aprendiz
Tiempo estimado: 90-120 min | Pre-requisitos: PF-007, PF-009
🖼️ Imagen de inicio del tema

[Moka con balanza azteca contando granos de cacao en grandes montones. Sofía observa los totales, Don Carlos señala los grupos, Citlalli con su codex anota los agregados.]
🎯 Aprenderás
🏛️ Visión mexica — Por Citlalli
"Los calpixqui mexicas reportaban al huey tlatoani con resúmenes: 'de Suchimilco llegaron 20 cargas de cacao este año'. Ese reporte era el resultado de AGRUPAR (por altépetl) y SUMAR (las cargas). GROUP BY + SUM = el reporte del calpixqui, en 500 años antes de SQL.
El poder de las funciones de agregación es exactamente ese: convertir 1000 filas de detalle en UNA fila de resumen. De 50,000 clientes individuales, una sola fila que dice 'total de clientes activos: 47,382'. Eso es lo que el negocio quiere ver: resúmenes accionables, no datos crudos."
📚 Bloque 1: Las 5 funciones de agregación básicas
SELECT COUNT() AS total_clientes FROM clientes;
SELECT COUNT(email) AS con_email FROM clientes; -- no cuenta NULL
SELECT SUM(total) AS ingresos FROM pedidos WHERE estado = 'pagado';
SELECT AVG(precio) AS precio_promedio FROM productos;
SELECT MIN(precio) AS mas_barato, MAX(precio) AS mas_caro FROM productos;📚 Bloque 2: GROUP BY — agrupar por categoría
-- Cuántos clientes por ciudad
SELECT ciudad, COUNT() AS total
FROM clientes
GROUP BY ciudad
ORDER BY total DESC;Sofía pregunta: "¿Por qué GROUP BY va después de WHERE y antes de ORDER BY?"
Don Carlos: "El orden lógico de SQL es:
📚 Bloque 3: HAVING — filtrar grupos
-- Solo ciudades con más de 5 clientes
SELECT ciudad, COUNT() AS total
FROM clientes
GROUP BY ciudad
HAVING COUNT() > 5
ORDER BY total DESC;Don Carlos: "WHERE filtra FILAS antes de agrupar. HAVING filtra GRUPOS después de agrupar. Es la diferencia más sutil y más importante de SQL agregado."
📚 Bloque 4: Patrones de reportes de negocio
-- Ingresos por mes en 2025
SELECT DATE_FORMAT(fecha_pedido, '%Y-%m') AS mes, SUM(total) AS ingresos
FROM pedidos
WHERE YEAR(fecha_pedido) = 2025 AND estado IN ('pagado', 'enviado', 'entregado')
GROUP BY mes
ORDER BY mes;-- Top 5 clientes por gasto
SELECT c.nombre, SUM(pe.total) AS gasto_total, COUNT(pe.id) AS num_pedidos
FROM clientes c
INNER JOIN pedidos pe ON c.id = pe.cliente_id
WHERE pe.estado != 'cancelado'
GROUP BY c.id, c.nombre
ORDER BY gasto_total DESC
LIMIT 5;
🤖 AI Mission
Pídele: "Tengo una tabla `ventas` con 10,000 filas. Dame 3 queries de GROUP BY que un dueño de tienda pediría para entender su negocio."
⚠️ Errores típicos
🧪 LAB 10.1 — "Reportes para el dueño de Tienda Tlalli"
Reporte 1: total de clientes por estado (activo/inactivo). Reporte 2: ventas totales por método de pago. Reporte 3: top 5 categorías por ingresos (necesita JOIN). Reporte 4: promedio de productos por pedido. Reporte 5: ciudades con más de 3 clientes (HAVING).
📝 Quest 10
✅ Respuestas
1-A, 2-B, 3-B, 4-B, 5-B, 6-C, 7-B, 8-A, 9-B, 10-B.
🏛️ Tras las huellas — Por Citlalli
"Los reportes del calpixqui al huey tlatoani eran el equivalente de los dashboards modernos. El tlacuilo tenía que resumir 371 altépetl en 5 minutos de lectura. Hoy un dashboard ejecutivo tiene 5 minutos de atención. El principio es el mismo: agregación + filtro + orden = decisión accionable."
📓 Cierre
1. El reporte más útil fue:
2. La diferencia WHERE/HAVING que más me costó fue:
3. Una decisión de negocio basada en estos reportes:
4. Cómo voy a usar GROUP BY en mi trabajo:
🚀 ¿Qué sigue?
Tema 11 — Subconsultas y CTEs: consultas que se leen como párrafos.