Saltar al contenido

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;
3
4-- ¿Cuánto hemos facturado en total?
5SELECT SUM(importe)::INTEGER AS ventas_totales FROM pedidos;
6
7-- ¿Cuál es el importe medio de un pedido (ticket medio)?
8SELECT ROUND(AVG(importe), 2) AS ticket_medio FROM pedidos;
9
10-- ¿Cuál es el pedido más grande y el más pequeño?
11SELECT
12 MAX(importe) AS pedido_mayor,
13 MIN(importe) AS pedido_menor
14FROM 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 NULLs
2SELECT COUNT(*) AS total_filas FROM clientes;
3
4-- COUNT(ciudad): cuenta filas donde ciudad NO es NULL
5-- (habrá menos si hay clientes sin ciudad)
6SELECT COUNT(ciudad) AS con_ciudad FROM clientes;
7
8-- COUNT(DISTINCT ciudad): ¿cuántas ciudades diferentes tenemos?
9SELECT COUNT(DISTINCT ciudad) AS ciudades_unicas FROM clientes;
10
11-- La diferencia en acción:
12SELECT
13 COUNT(*) AS filas_totales,
14 COUNT(ciudad) AS con_ciudad,
15 COUNT(DISTINCT ciudad) AS ciudades_distintas
16FROM 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 query
2SELECT
3 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_pedido
10FROM pedidos
11WHERE 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 decimales
2SELECT SUM(importe)::INTEGER AS total FROM pedidos;
3
4-- AVG da muchos decimales — usa ROUND
5SELECT ROUND(AVG(importe), 2) AS media FROM pedidos;
6
7-- Combina ambos para reportes limpios
8SELECT
9 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_cliente
12FROM pedidos
13WHERE 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 pedidos
2SELECT
3 MIN(fecha) AS primer_pedido,
4 MAX(fecha) AS ultimo_pedido,
5 MAX(fecha) - MIN(fecha) AS dias_de_actividad
6FROM pedidos;
7
8-- Rango de precios por categoría
9SELECT
10 MIN(precio) AS precio_minimo,
11 MAX(precio) AS precio_maximo,
12 MAX(precio) - MIN(precio) AS rango_precios
13FROM 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 2024
2SELECT
3 COUNT(*) AS pedidos_2024,
4 SUM(importe)::INTEGER AS total_2024,
5 ROUND(AVG(importe), 2) AS ticket_medio_2024
6FROM pedidos
7WHERE 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

[01]

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.

Cargando editor...
[02]

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).

Cargando editor...
[03]

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.

Cargando editor...
[04]

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).

Cargando editor...
[05]

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.

Cargando editor...

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...