lección 5
Contar, sumar y promediar: funciones de agregación
COUNT, SUM, AVG, MIN y MAX — convertir miles de filas en un solo número que responde una pregunta de negocio.
⏱ 55 min
Hasta ahora, tus consultas devuelven filas individuales: cada cliente, cada pedido, cada producto. Pero cuando el director financiero pregunta "¿cuánto vendimos este trimestre?", no quiere ver 14.000 filas de pedidos — quiere UN número. Cuando marketing pregunta "¿cuál es nuestro ticket medio?", espera una respuesta, no una tabla interminable. Las funciones de agregación son exactamente eso: toman N filas y las colapsan en un solo valor que responde una pregunta de negocio.
La analogía perfecta: imagina que tienes un montón de recibos de compra sobre la mesa. "Contar" los recibos es COUNT. "Sumar" todos los importes es SUM. "Calcular la media" es AVG. "Buscar el más alto" es MAX. Son operaciones que hacemos mentalmente con datos pequeños, pero que SQL ejecuta sobre millones de filas en milisegundos.
### Las 5 funciones de agregación fundamentales
- COUNT(*) — cuenta el número TOTAL de filas (incluye filas con NULLs)
- COUNT(columna) — cuenta filas donde esa columna NO es NULL
- COUNT(DISTINCT columna) — cuenta valores ÚNICOS (sin repetir)
- SUM(columna) — suma todos los valores numéricos de la columna
- AVG(columna) — media aritmética (ignora NULLs automáticamente)
- MAX(columna) — el valor más grande (funciona con números, fechas y texto)
- MIN(columna) — el valor más pequeño
1-- ¿Cuántos clientes tenemos en total?2SELECT COUNT(*) AS total_clientes FROM clientes;34-- ¿Cuánto hemos facturado en total?5SELECT SUM(importe)::INTEGER AS ventas_totales FROM pedidos;67-- ¿Cuál es el importe medio de un pedido (ticket medio)?8SELECT ROUND(AVG(importe), 2) AS ticket_medio FROM pedidos;910-- ¿Cuál es el pedido más grande y el más pequeño?11SELECT12 MAX(importe) AS pedido_mayor,13 MIN(importe) AS pedido_menor14FROM pedidos;
Cada función toma miles de filas y devuelve UN número
### COUNT(*) vs COUNT(columna) vs COUNT(DISTINCT)
Esta es una distinción que separa a los juniors de los mids. COUNT tiene tres variantes con comportamientos muy diferentes, y confundirlas genera bugs sutiles en reportes financieros:
1-- COUNT(*): cuenta TODAS las filas, incluso las que tienen NULLs2SELECT COUNT(*) AS total_filas FROM clientes;34-- COUNT(ciudad): cuenta filas donde ciudad NO es NULL5-- (habrá menos si hay clientes sin ciudad)6SELECT COUNT(ciudad) AS con_ciudad FROM clientes;78-- COUNT(DISTINCT ciudad): ¿cuántas ciudades diferentes tenemos?9SELECT COUNT(DISTINCT ciudad) AS ciudades_unicas FROM clientes;1011-- La diferencia en acción:12SELECT13 COUNT(*) AS filas_totales,14 COUNT(ciudad) AS con_ciudad,15 COUNT(DISTINCT ciudad) AS ciudades_distintas16FROM clientes;
Tres COUNTs, tres significados — elige el correcto
Consejo de senior: en mi segundo trabajo, un reporte financiero daba 2% menos de ventas que el ERP. Después de 3 horas de debug, descubrí que alguien usaba COUNT(importe) en vez de COUNT(*), y había pedidos con importe NULL (pagos pendientes). COUNT(columna) ignora NULLs silenciosamente. Siempre verifica cuál estás usando.
### Combinar múltiples agregaciones en una sola query
No necesitas ejecutar 5 queries separadas para obtener 5 métricas. SQL permite combinar todas las funciones de agregación en un solo SELECT. Este patrón es la base de cualquier dashboard:
1-- Dashboard de métricas en UNA sola query2SELECT3 COUNT(*) AS total_pedidos,4 COUNT(DISTINCT cliente_id) AS clientes_activos,5 SUM(importe)::INTEGER AS facturacion_total,6 ROUND(AVG(importe), 2) AS ticket_medio,7 MAX(importe) AS pedido_max,8 MIN(importe) AS pedido_min,9 MAX(fecha) AS ultimo_pedido10FROM pedidos11WHERE estado = 'completado';
Un dashboard completo en una sola consulta
### SUM y AVG: cuidado con los tipos
SUM devuelve un número potencialmente enorme (puede desbordar INTEGER si sumas millones de valores). AVG siempre devuelve un DOUBLE con muchos decimales. ROUND es tu amigo para hacer los resultados legibles:
1-- SUM puede dar números enormes — usa ::INTEGER para truncar decimales2SELECT SUM(importe)::INTEGER AS total FROM pedidos;34-- AVG da muchos decimales — usa ROUND5SELECT ROUND(AVG(importe), 2) AS media FROM pedidos;67-- Combina ambos para reportes limpios8SELECT9 SUM(importe)::INTEGER AS total_ventas,10 ROUND(AVG(importe), 2) AS ticket_medio,11 ROUND(SUM(importe) / COUNT(DISTINCT cliente_id), 2) AS gasto_medio_por_cliente12FROM pedidos13WHERE estado = 'completado';
ROUND y ::INTEGER para resultados legibles en reportes
### MIN y MAX: no solo para números
MAX y MIN funcionan con cualquier tipo que se pueda ordenar: números, fechas, e incluso texto (orden alfabético). Esto los hace extremadamente útiles para encontrar rangos temporales:
1-- Rango de fechas de los pedidos2SELECT3 MIN(fecha) AS primer_pedido,4 MAX(fecha) AS ultimo_pedido,5 MAX(fecha) - MIN(fecha) AS dias_de_actividad6FROM pedidos;78-- Rango de precios por categoría9SELECT10 MIN(precio) AS precio_minimo,11 MAX(precio) AS precio_maximo,12 MAX(precio) - MIN(precio) AS rango_precios13FROM productos;
MIN/MAX con fechas: descubrir el rango temporal de tus datos
Las funciones de agregación ignoran NULLs (excepto COUNT(*)). Si tienes 100 pedidos y 5 tienen importe NULL, AVG(importe) calcula la media de los 95 que SÍ tienen valor. Esto puede sesgar tus resultados sin que te des cuenta.
### Agregación con filtro: WHERE antes de agregar
Puedes combinar WHERE con funciones de agregación para calcular métricas sobre un subconjunto de datos. El WHERE se ejecuta PRIMERO (filtra filas) y LUEGO la agregación actúa sobre las filas que pasaron el filtro:
1-- Solo pedidos completados de 20242SELECT3 COUNT(*) AS pedidos_2024,4 SUM(importe)::INTEGER AS total_2024,5 ROUND(AVG(importe), 2) AS ticket_medio_20246FROM pedidos7WHERE estado = 'completado'8 AND fecha >= '2024-01-01';
WHERE filtra ANTES, la agregación actúa DESPUÉS
UNION ALL: a veces necesitas apilar los resultados de varias queries en una sola tabla. UNION ALL combina dos SELECTs verticalmente (requisito: mismo número de columnas y tipos compatibles). Ejemplo: un SELECT para completados + UNION ALL + un SELECT para cancelados → una tabla con ambos. UNION ALL mantiene duplicados (más rápido); UNION sin ALL los elimina. Lo usarás en los ejercicios siguientes.
## ejercicios
Dashboard de métricas del e-commerce
El CEO quiere un resumen ejecutivo. Calcula en una sola consulta: total de pedidos, clientes activos (distintos), suma total de ventas (como entero), ticket medio (redondeado a 2 decimales) y la fecha del último pedido. Solo cuenta pedidos completados.
Clientes activos vs registrados
Marketing quiere saber qué porcentaje de clientes registrados han hecho al menos un pedido. Muestra: total de clientes registrados, clientes que han comprado (DISTINCT cliente_id en pedidos) y el porcentaje de activación (redondeado a 1 decimal).
Rango de precios del catálogo
El equipo de pricing necesita un análisis del catálogo: producto más barato, más caro, precio medio, rango (max-min) y total de productos. Todo en una query.
Comparar estados de pedidos
Finanzas necesita desglosar pedidos por estado: para cada estado (completado, cancelado, devuelto), muestra cuántos pedidos hay, el importe total y el ticket medio. Usa WHERE con cada estado en queries separadas o una CTE (extra).
Comparar primer y segundo semestre
El CFO quiere comparar las ventas completadas del primer semestre 2024 (ene-jun) vs segundo semestre 2024 (jul-dic). Para cada semestre: pedidos, total facturado y ticket medio.
Regístrate para guardar tu progreso.
## comentarios
Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.
Inicia sesión para comentar y responder.
cargando comentarios...