...

HAVING en SQL: filtra grupos, no filas

Cuando agrupas datos con GROUP BY y utilizas funciones de agregación (COUNT, SUM, AVG, etc.), a menudo necesitas filtrar esos grupos (por ejemplo, «mostrar sólo departamentos con más de 5 empleados» o «mostrar productos con ventas totales superiores a 100k»). Para eso existe la cláusula HAVING: que permite aplicar condiciones sobre resultados agregados, es decir, filtrar grupos en lugar de filas.

En este artículo verás:

  • la diferencia práctica entre WHERE y HAVING,
  • la sintaxis y varios ejemplos,
  • combinaciones WHERE + GROUP BY + HAVING,
  • buenas prácticas y recomendaciones de rendimiento,
  • notas de compatibilidad entre SQL Server, PostgreSQL y MySQL.
    Todos los ejemplos son ejecutables en db-fiddle.com.

¿Qué hace HAVING y cómo se diferencia de WHERE?

  • WHERE filtra filas individuales antes de que se agrupe la información.
  • HAVING filtra los grupos resultantes de GROUP BY, después de calcular agregados.

Regla simple: usa WHERE para condiciones sobre columnas no agregadas; usa HAVING para condiciones que involucran funciones agregadas.


Sintaxis básica

SELECT columna_agrupada, AGG(columna) AS alias
FROM tabla
GROUP BY columna_agrupada
HAVING condición_agregada;

Ejemplo mínimo:

SELECT departamento, COUNT(*) AS total_empleados
FROM empleados
GROUP BY departamento
HAVING COUNT(*) > 5;

Estructura y datos para practicar

CREATE TABLE departamentos (
  id_departamento INTEGER PRIMARY KEY,
  nombre_departamento TEXT NOT NULL
);

CREATE TABLE empleados (
  id_empleado INTEGER PRIMARY KEY,
  nombre TEXT NOT NULL,
  salario DECIMAL(10,2) NOT NULL,
  id_departamento INTEGER
);

INSERT INTO departamentos (id_departamento, nombre_departamento) VALUES
(1,'Ventas'),(2,'Marketing'),(3,'Finanzas'),(4,'Desarrollo');

INSERT INTO empleados (id_empleado, nombre, salario, id_departamento) VALUES
(1,'Ana',70000,1),
(2,'Luis',90000,1),
(3,'Marta',60000,2),
(4,'Carlos',50000,3),
(5,'Sofía',80000,1),
(6,'Diego',45000,NULL),
(7,'Laura',72000,4),
(8,'Pedro',48000,2);

Ejemplo A — Contar empleados por departamento y filtrar grupos

SELECT d.nombre_departamento, COUNT(e.id_empleado) AS total_empleados
FROM empleados e
JOIN departamentos d ON e.id_departamento = d.id_departamento
GROUP BY d.nombre_departamento
HAVING COUNT(e.id_empleado) > 2;

Explicación: primero se agrupa por departamento, luego HAVING selecciona sólo los departamentos con más de 2 empleados.

Ejemplo B — SUM con HAVING (total salarial por departamento)

SELECT d.nombre_departamento, SUM(e.salario) AS total_salarios
FROM empleados e
JOIN departamentos d ON e.id_departamento = d.id_departamento
GROUP BY d.nombre_departamento
HAVING SUM(e.salario) >= 200000;

Explicación: útil para reportes financieros por unidad.

Ejemplo C — WHERE + GROUP BY + HAVING (filtrado previo y por agregados)

SELECT d.nombre_departamento, AVG(e.salario) AS salario_promedio
FROM empleados e
JOIN departamentos d ON e.id_departamento = d.id_departamento
WHERE e.salario > 50000          -- filtra filas antes de agrupar
GROUP BY d.nombre_departamento
HAVING AVG(e.salario) > 70000;   -- filtra grupos según el agregado

Ejemplo D — Nota sobre alias en HAVING (portabilidad)

Algunos motores permiten usar un alias en HAVING, p. ej.:

SELECT d.nombre_departamento, COUNT(*) AS total
FROM empleados e
JOIN departamentos d ON e.id_departamento = d.id_departamento
GROUP BY d.nombre_departamento
HAVING total > 2; -- MySQL suele permitirlo

Recomendación: por portabilidad usa la función agregada directa en HAVING (HAVING COUNT(*) > 2) en lugar del alias.


Casos especiales

  • HAVING sin GROUP BY: MySQL permite HAVING sin GROUP BY (comportamiento no estándar: trata el conjunto como un único grupo), pero esto no es portable ni recomendable.
  • Usar HAVING para filtrar filas individuales (por ejemplo, HAVING salario > 50000 sin agregación) es conceptualmente incorrecto: en esos casos usa WHERE.

Rendimiento y buenas prácticas

  1. Filtra antes con WHERE: reduce el conjunto de filas antes de agrupar para ahorrar trabajo en el agregador.
  2. Usa HAVING sólo para condiciones agregadas (COUNT, SUM, AVG…).
  3. Indexes: garantizar índices en columnas usadas en JOIN o en WHERE mejora la velocidad previa a la agregación; las agregaciones en sí mismas seguirán necesitando procesamiento.
  4. Evita funciones en la columna de agrupamiento (p. ej. GROUP BY YEAR(fecha)) si puedes reescribir la condición con rangos (BETWEEN) — las funciones impiden usar índices.
  5. Comprueba el plan de ejecución en consultas pesadas (EXPLAIN / EXPLAIN ANALYZE) y evalúa el uso de CTEs o agregaciones parciales si corresponde.
  6. Para grandes volúmenes, evaluar pre-aggregaciones o tablas resumen puede ser más eficiente que hacer agregación on-the-fly.

Diferencias entre motores (SQL Server, PostgreSQL, MySQL)

  • SQL Server
    • HAVING es estándar y funciona como se espera.
    • Para optimización puedes usar índices, vistas indexadas (indexed views) y revisar el plan de ejecución.
  • PostgreSQL
    • Soporta HAVING y, además, la cláusula FILTER (WHERE ...) para agregados es muy útil: SELECT COUNT(*) FILTER (WHERE estado = 'activo') AS activos, COUNT(*) FILTER (WHERE estado = 'inactivo') AS inactivos FROM usuarios;
    • PostgreSQL suele ser muy eficiente en agregaciones con configuración adecuada y estadísticas.
  • MySQL
    • Soporta HAVING.
    • MySQL históricamente permite HAVING sin GROUP BY (comportamiento no estándar).
    • Para portabilidad y claridad, prefiera usar la expresión agregada en HAVING en vez de alias.

Conclusión

HAVING es la herramienta correcta cuando necesitas filtrar resultados agregados después de aplicar GROUP BY. Para consultas eficientes: filtra primero con WHERE, agrupa con GROUP BY y luego aplica HAVING sólo a condiciones agregadas. Practica los ejemplos en db-fiddle.com para interiorizar la lógica y medir rendimiento real.

Deja un comentario

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.