Día 3 – Funciones y agregaciones: COUNT, SUM, AVG, GROUP BY, HAVING

Hoy vas a dar un salto importante. Hasta ahora has trabajado con filas individuales: seleccionar, filtrar, ordenar. Pero en la realidad muchas veces necesitas resumir información: contar cuántos registros hay, calcular totales, promedios, máximos…

Esto es lo que hacen las funciones de agregación. Te permiten pasar de «mostrar datos» a «analizar datos».

Escenario del día

Imagina que tu jefe te pide un informe urgente:

  • ¿Cuántos pedidos tenemos en total?
  • ¿Cuál es el valor promedio de un pedido?
  • ¿Qué usuario ha gastado más dinero?
  • ¿Cuántos productos vendemos por categoría?

Sin SQL tendrías que exportar todo a Excel y hacer las cuentas manualmente. Con lo que aprenderás hoy, lo resuelves en minutos.


1. Funciones de agregación básicas

Las funciones de agregación toman múltiples filas y devuelven un solo valor. Son perfectas para obtener estadísticas rápidas.

COUNT() – Contar registros

La más usada de todas. Cuenta cuántas filas hay.

-- ¿Cuántos usuarios tenemos?
SELECT COUNT(*) AS total_usuarios
FROM usuarios;

Resultado: Un número, por ejemplo 10.

-- ¿Cuántos productos hay en la categoría Electronica?
SELECT COUNT(*) AS productos_electronica
FROM productos
WHERE categoria = 'Electronica';

Diferencia importante:

  • COUNT(*) → Cuenta todas las filas (incluye NULLs)
  • COUNT(columna) → Cuenta solo filas donde esa columna NO es NULL
-- Contar productos que tienen stock
SELECT COUNT(stock) AS productos_con_stock
FROM productos
WHERE stock > 0;

SUM() – Sumar valores

Suma todos los valores de una columna numérica.

-- ¿Cuánto vale todo nuestro inventario?
SELECT SUM(precio * stock) AS valor_total_inventario
FROM productos;
-- Total de ventas (suma de todos los pedidos)
SELECT SUM(total) AS ventas_totales
FROM pedidos;

⚠️ Importante: SUM() solo funciona con columnas numéricas. No puedes sumar textos ni fechas.

AVG() – Calcular promedio

Calcula la media aritmética de una columna.

-- ¿Cuál es el precio promedio de nuestros productos?
SELECT AVG(precio) AS precio_promedio
FROM productos;

El resultado puede tener muchos decimales. Usa ROUND() para redondear:

-- Precio promedio con 2 decimales
SELECT ROUND(AVG(precio), 2) AS precio_promedio
FROM productos;
-- Valor promedio de un pedido
SELECT ROUND(AVG(total), 2) AS ticket_promedio
FROM pedidos;

MIN() y MAX() – Valores mínimo y máximo

Encuentra el valor más pequeño o más grande de una columna.

-- ¿Cuál es el producto más barato y el más caro?
SELECT 
    MIN(precio) AS producto_mas_barato,
    MAX(precio) AS producto_mas_caro
FROM productos;
-- Primer y último registro de usuario
SELECT 
    MIN(fecha_registro) AS primer_usuario,
    MAX(fecha_registro) AS ultimo_usuario
FROM usuarios;

Combinar varias agregaciones

Puedes usar varias funciones en la misma consulta:

-- Estadísticas completas de productos
SELECT 
    COUNT(*) AS total_productos,
    ROUND(AVG(precio), 2) AS precio_promedio,
    MIN(precio) AS precio_minimo,
    MAX(precio) AS precio_maximo,
    SUM(stock) AS unidades_totales
FROM productos;

2. GROUP BY – Agrupar por categorías

Aquí es donde todo se vuelve interesante. GROUP BY te permite agrupar filas que tienen algo en común y aplicar funciones de agregación a cada grupo.

Concepto básico

Sin GROUP BY:

-- Esto te da UN solo número (total de productos)
SELECT COUNT(*) FROM productos;

Con GROUP BY:

-- Esto te da UN número POR CADA categoría
SELECT 
    categoria,
    COUNT(*) AS cantidad
FROM productos
GROUP BY categoria;

¿Cómo funciona?

  1. SQL agrupa todas las filas que tienen la misma categoria
  2. Para cada grupo, aplica COUNT(*)
  3. Te devuelve una fila por cada grupo

Resultado:

categoria      | cantidad
---------------|----------
Electronica    | 4
Accesorios     | 4
Muebles        | 1
Almacenamiento | 1

Ejemplos prácticos

-- ¿Cuántos productos tenemos de cada categoría?
SELECT 
    categoria,
    COUNT(*) AS total_productos
FROM productos
GROUP BY categoria
ORDER BY total_productos DESC;
-- Valor promedio por categoría
SELECT 
    categoria,
    ROUND(AVG(precio), 2) AS precio_promedio,
    COUNT(*) AS cantidad_productos
FROM productos
GROUP BY categoria
ORDER BY precio_promedio DESC;
-- Usuarios por país
SELECT 
    pais,
    COUNT(*) AS total_usuarios
FROM usuarios
GROUP BY pais
ORDER BY total_usuarios DESC;

Múltiples agregaciones por grupo

Puedes calcular varias estadísticas a la vez para cada grupo:

-- Análisis completo por categoría
SELECT 
    categoria,
    COUNT(*) AS productos,
    ROUND(AVG(precio), 2) AS precio_promedio,
    MIN(precio) AS precio_min,
    MAX(precio) AS precio_max,
    SUM(stock) AS stock_total
FROM productos
GROUP BY categoria
ORDER BY precio_promedio DESC;

Agrupar por varias columnas

Puedes agrupar por más de una columna:

-- Usuarios por país y año de registro
SELECT 
    pais,
    EXTRACT(YEAR FROM fecha_registro) AS año,
    COUNT(*) AS registros
FROM usuarios
GROUP BY pais, EXTRACT(YEAR FROM fecha_registro)
ORDER BY pais, año;

Esto agrupa primero por país, y dentro de cada país, por año.


3. HAVING – Filtrar grupos

Ya conoces WHERE para filtrar filas individuales. Pero ¿cómo filtras grupos después de agrupar? Ahí entra HAVING.

Diferencia clave: WHERE vs HAVING

WHERE → Filtra filas ANTES de agrupar HAVING → Filtra grupos DESPUÉS de agrupar

-- ❌ ESTO NO FUNCIONA (no puedes usar COUNT en WHERE)
SELECT categoria, COUNT(*) AS total
FROM productos
WHERE COUNT(*) > 3  -- ERROR
GROUP BY categoria;
-- ✅ CORRECTO (usa HAVING para filtrar grupos)
SELECT categoria, COUNT(*) AS total
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3;

Ejemplos con HAVING

-- Categorías con más de 3 productos
SELECT 
    categoria,
    COUNT(*) AS total_productos
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3
ORDER BY total_productos DESC;
-- Países con más de 2 usuarios
SELECT 
    pais,
    COUNT(*) AS total_usuarios
FROM usuarios
GROUP BY pais
HAVING COUNT(*) > 2;
-- Categorías donde el precio promedio supera los 200€
SELECT 
    categoria,
    ROUND(AVG(precio), 2) AS precio_promedio,
    COUNT(*) AS productos
FROM productos
GROUP BY categoria
HAVING AVG(precio) > 200;

Combinar WHERE y HAVING

Puedes usar ambos en la misma consulta:

-- Categorías con más de 2 productos en stock
-- que cuestan más de 100€
SELECT 
    categoria,
    COUNT(*) AS productos_caros,
    ROUND(AVG(precio), 2) AS precio_promedio
FROM productos
WHERE precio > 100           -- Filtro ANTES de agrupar
GROUP BY categoria
HAVING COUNT(*) > 2          -- Filtro DESPUÉS de agrupar
ORDER BY precio_promedio DESC;

Orden de ejecución:

  1. FROM → Qué tabla usar
  2. WHERE → Filtra filas individuales
  3. GROUP BY → Agrupa filas
  4. HAVING → Filtra grupos
  5. SELECT → Calcula las columnas
  6. ORDER BY → Ordena el resultado

4. Casos prácticos del día a día

Ahora que conoces las herramientas, veamos consultas útiles para informes reales.

Top categorías por ventas

-- ¿Qué categorías generan más ingresos?
SELECT 
    p.categoria,
    COUNT(dp.id) AS unidades_vendidas,
    ROUND(SUM(dp.precio_unitario * dp.cantidad), 2) AS ingresos_totales
FROM detalle_pedidos dp
JOIN productos p ON dp.producto_id = p.id
GROUP BY p.categoria
ORDER BY ingresos_totales DESC;

Usuarios más activos

-- Usuarios con más pedidos
SELECT 
    u.nombre,
    u.email,
    COUNT(pe.id) AS total_pedidos,
    ROUND(SUM(pe.total), 2) AS gasto_total
FROM usuarios u
JOIN pedidos pe ON u.id = pe.usuario_id
GROUP BY u.id, u.nombre, u.email
ORDER BY total_pedidos DESC;

Productos más vendidos

-- Top 5 productos por cantidad vendida
SELECT 
    p.nombre,
    SUM(dp.cantidad) AS unidades_vendidas,
    ROUND(SUM(dp.precio_unitario * dp.cantidad), 2) AS ingresos
FROM productos p
JOIN detalle_pedidos dp ON p.id = dp.producto_id
GROUP BY p.id, p.nombre
ORDER BY unidades_vendidas DESC
LIMIT 5;

Análisis de inventario

-- Productos con stock bajo (menos de 20 unidades)
SELECT 
    categoria,
    COUNT(*) AS productos_stock_bajo,
    SUM(stock) AS unidades_totales
FROM productos
WHERE stock < 20
GROUP BY categoria;

5. Errores comunes (y cómo evitarlos)

Error 1: Usar columnas sin agrupar

-- ❌ ESTO DA ERROR
SELECT categoria, nombre, COUNT(*)
FROM productos
GROUP BY categoria;

Problema: nombre no está en GROUP BY ni es una agregación.

Solución: Solo puedes seleccionar columnas que están en GROUP BY o son funciones de agregación.

-- ✅ CORRECTO
SELECT categoria, COUNT(*) AS total
FROM productos
GROUP BY categoria;

Error 2: Olvidar el alias

-- Poco claro
SELECT COUNT(*) FROM productos;
-- ✅ Mejor
SELECT COUNT(*) AS total_productos FROM productos;

Los alias hacen tu consulta más legible.

Error 3: Confundir WHERE con HAVING

-- ❌ WHERE no puede usar funciones de agregación
SELECT categoria, COUNT(*) AS total
FROM productos
WHERE COUNT(*) > 3
GROUP BY categoria;
-- ✅ Usa HAVING
SELECT categoria, COUNT(*) AS total
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3;

6. Ejercicios prácticos

Intenta resolver estos ejercicios antes de ver las soluciones.

  1. Cuenta cuántos usuarios hay por país
  2. Calcula el precio promedio de productos por categoría (con 2 decimales)
  3. Muestra solo las categorías que tienen más de 3 productos
  4. Encuentra el total de unidades en stock por categoría
  5. Muestra los usuarios que han hecho más de 1 pedido
  6. Calcula cuántos productos hay en cada rango de precio:
    • Baratos (menos de 100€)
    • Medios (100€ – 300€)
    • Caros (más de 300€)
  7. Encuentra las categorías donde el stock total supera las 30 unidades
  8. Calcula el ticket promedio (valor medio de los pedidos)

💡 Consejo: Empieza siempre por SELECT y FROM, luego añade GROUP BY, y finalmente HAVING si lo necesitas.


7. Soluciones

Solución Ejercicio 1

SELECT 
    pais,
    COUNT(*) AS total_usuarios
FROM usuarios
GROUP BY pais
ORDER BY total_usuarios DESC;

Solución Ejercicio 2

SELECT 
    categoria,
    ROUND(AVG(precio), 2) AS precio_promedio
FROM productos
GROUP BY categoria
ORDER BY precio_promedio DESC;

Solución Ejercicio 3

SELECT 
    categoria,
    COUNT(*) AS total_productos
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3;

Solución Ejercicio 4

SELECT 
    categoria,
    SUM(stock) AS stock_total
FROM productos
GROUP BY categoria
ORDER BY stock_total DESC;

Solución Ejercicio 5

SELECT 
    u.nombre,
    u.email,
    COUNT(p.id) AS total_pedidos
FROM usuarios u
JOIN pedidos p ON u.id = p.usuario_id
GROUP BY u.id, u.nombre, u.email
HAVING COUNT(p.id) > 1
ORDER BY total_pedidos DESC;

Solución Ejercicio 6

SELECT 
    CASE 
        WHEN precio < 100 THEN 'Barato'
        WHEN precio BETWEEN 100 AND 300 THEN 'Medio'
        ELSE 'Caro'
    END AS rango_precio,
    COUNT(*) AS cantidad_productos
FROM productos
GROUP BY rango_precio
ORDER BY 
    CASE rango_precio
        WHEN 'Barato' THEN 1
        WHEN 'Medio' THEN 2
        WHEN 'Caro' THEN 3
    END;

Solución Ejercicio 7

SELECT 
    categoria,
    SUM(stock) AS stock_total
FROM productos
GROUP BY categoria
HAVING SUM(stock) > 30;

Solución Ejercicio 8

SELECT 
    ROUND(AVG(total), 2) AS ticket_promedio
FROM pedidos;

Resumen del Día 3

¡Excelente trabajo! Ahora dominas las herramientas para analizar datos y generar informes.

Has aprendido:

Funciones de agregación: COUNT(), SUM(), AVG(), MIN(), MAX()
Agrupar datos: GROUP BY para análisis por categorías
Filtrar grupos: HAVING para condiciones sobre agregaciones
Combinar todo: Consultas complejas para informes reales
Orden de ejecución: FROMWHEREGROUP BYHAVINGSELECTORDER BY

Estas técnicas te permiten pasar de «mostrar datos» a «analizar datos». Son fundamentales para generar reportes, dashboards y KPIs.


Siguiente paso

En el Día 4 aprenderás JOINs: cómo relacionar varias tablas para obtener información completa. Por ejemplo, unir usuarios con pedidos para ver quién compró qué.

Hasta ahora has trabajado con una sola tabla a la vez. Mañana verás cómo combinar información de múltiples tablas, que es donde SQL realmente brilla.

¡Nos vemos en el Día 4!