Bases de Datos

¿Cuál es la diferencia entre WHERE y HAVING en SQL?

Autorangel cruz
Actualizado
Publicado
Lectura5 min de lectura

La diferencia corta: WHERE filtra filas antes de agrupar y HAVING filtra el resultado después. Todo lo demás que vas a leer sobre las dos cláusulas sale de ahí: por qué una acepta alias y la otra no, por qué solo una funciona con COUNT() o SUM(), y por qué en la práctica conviene usar WHERE siempre que puedas.

El orden de ejecución lo explica todo

SQL no se ejecuta en el orden en que lo escribes. El motor lo resuelve más o menos así:

FROM  →  WHERE  →  GROUP BY  →  HAVING  →  SELECT  →  ORDER BY  →  LIMIT

Fíjate dónde caen las dos: WHERE actúa antes de GROUP BY y de SELECT; HAVING actúa después de agrupar. Con eso en la cabeza, el resto deja de ser una lista de reglas que memorizar.

Con datos de ejemplo

Digamos que tenemos esta tabla:

CREATE TABLE `table` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `value` int(10) unsigned NOT NULL,
 PRIMARY KEY (`id`),
 KEY `value` (`value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Con 10 filas, donde id y value van de 1 a 10:

INSERT INTO `table`(`id`, `value`) VALUES (1, 1),(2, 2),(3, 3),(4, 4),(5, 5),(6, 6),(7, 7),(8, 8),(9, 9),(10, 10);

Si ejecutamos estas dos consultas:

SELECT `value` v FROM `table` WHERE `value`>5; -- 5 filas
SELECT `value` v FROM `table` HAVING `value`>5; -- 5 filas

Obtenemos exactamente el mismo resultado. Y ya se ve algo que sorprende a mucha gente: HAVING funciona sin GROUP BY. En MySQL es válido, aunque casi nunca sea lo que quieres.

La primera diferencia: los alias

Si intentamos filtrar por el alias con WHERE:

SELECT `value` v FROM `table` WHERE `v`>5;

Obtenemos:

Error #1054 - Unknown column 'v' in 'where clause'

Pero con HAVING sí funciona:

SELECT `value` v FROM `table` HAVING `v`>5; -- 5 filas

La razón está en el orden de arriba: cuando WHERE se ejecuta, el SELECT todavía no ha corrido, así que el alias v no existe. Cuando llega HAVING, la proyección ya está hecha y el alias sí está disponible.

La diferencia que de verdad importa: agregados

El caso para el que existe HAVING no es filtrar filas sueltas, es filtrar grupos. Y ahí WHERE no puede sustituirlo.

Supongamos una tabla de pedidos y esta pregunta: qué clientes tienen más de 3 pedidos pagados.

SELECT cliente_id, COUNT(*) AS pedidos
FROM pedidos
WHERE estado = 'pagado'
GROUP BY cliente_id
HAVING pedidos > 3;

Las dos cláusulas trabajan juntas y cada una hace algo distinto:

  • WHERE estado = 'pagado' descarta filas antes de agrupar. Los pedidos cancelados no llegan a contarse.
  • HAVING pedidos > 3 descarta grupos ya formados. Solo puede evaluarse cuando el COUNT() existe.

Intentar meter el agregado en el WHERE falla, y no por capricho:

SELECT cliente_id, COUNT(*) AS pedidos
FROM pedidos
WHERE COUNT(*) > 3          -- Error #1111 - Invalid use of group function
GROUP BY cliente_id;

Cuando WHERE se ejecuta, los grupos no existen todavía, así que COUNT(*) no tiene nada que contar.

Y al revés: mover el filtro de estado al HAVING no da un error, da un resultado distinto, que es peor porque parece que funciona:

-- Cuenta TODOS los pedidos y luego filtra grupos. No es lo mismo.
SELECT cliente_id, COUNT(*) AS pedidos
FROM pedidos
GROUP BY cliente_id
HAVING pedidos > 3;

Aquí los pedidos cancelados sí entran en el recuento. Es el error clásico de este tema: la consulta corre, devuelve filas, y los números están mal.

Rendimiento: por qué preferir WHERE

Usemos EXPLAIN sobre las dos consultas equivalentes del principio:

EXPLAIN SELECT `value` v FROM `table` WHERE `value`>5;
+----+-------------+-------+-------+---------------+-------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key   | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+-------+---------+------+------+--------------------------+
|  1 | SIMPLE      | table | range | value         | value | 4       | NULL |    5 | Using where; Using index |
+----+-------------+-------+-------+---------------+-------+---------+------+------+--------------------------+


EXPLAIN SELECT `value` v FROM `table` having `value`>5;
+----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key   | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
|  1 | SIMPLE      | table | index | NULL          | value | 4       | NULL |   10 | Using index |
+----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+

Las dos usan el índice, pero mira la columna rows y la columna type:

  • Con WHERE: type: range y 5 filas examinadas. El motor aprovecha el índice para saltar directo al rango que cumple la condición.
  • Con HAVING: type: index y 10 filas examinadas. Recorre el índice entero y descarta al final.

En una tabla de 10 filas da igual. En una de diez millones, es la diferencia entre una consulta instantánea y un escaneo completo. Regla práctica: si la condición se puede expresar sin agregados, va en WHERE. Deja HAVING solo para lo que no se puede filtrar antes de agrupar.

Resumen

WHERE HAVING
Cuándo actúa Antes de GROUP BY Después de GROUP BY
Sobre qué filtra Filas individuales Grupos ya formados
Acepta alias del SELECT No
Acepta funciones agregadas No
Necesita GROUP BY No No (pero es su caso normal)
Aprovecha índices para reducir filas No

Preguntas frecuentes

¿Puedo usar WHERE y HAVING en la misma consulta?

Sí, y es lo habitual en consultas con agregados. WHERE recorta las filas que entran al grupo y HAVING descarta los grupos que no cumplen. Son complementarias, no alternativas.

¿Se puede usar HAVING sin GROUP BY?

En MySQL sí: trata el resultado como un único grupo y el filtro se aplica al final. Funciona, pero si no hay agregados de por medio lo que quieres es WHERE, que además usa mejor los índices.

¿Por qué WHERE no acepta alias?

Porque cuando se evalúa, el SELECT todavía no se ha ejecutado y el alias no existe. HAVING corre después de la proyección, así que para él el alias ya está definido.

¿Por qué da error Invalid use of group function en el WHERE?

Porque estás usando COUNT(), SUM(), AVG() o similar en una cláusula que se ejecuta antes de que existan los grupos. Ese filtro va en HAVING.

¿Cuál es más rápido, WHERE o HAVING?

WHERE, cuando las dos pueden expresar la misma condición. Filtra antes y permite al motor usar el índice para examinar menos filas, como se ve en el EXPLAIN de arriba: 5 filas frente a 10 en el mismo ejemplo.

¿Esto aplica solo a MySQL?

El orden de ejecución y la distinción entre filtrar filas y filtrar grupos son parte del estándar SQL, así que el concepto vale en PostgreSQL, SQL Server, SQLite y el resto. Lo que cambia entre motores son los detalles, como la tolerancia de MySQL a HAVING sin GROUP BY o a columnas no agrupadas en el SELECT.

Lectura relacionada en MySQL: tamaños máximos de TEXT, TINYTEXT y MEDIUMTEXT.