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!

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.

ACEPTAR
Aviso de cookies