¿Cuál es la diferencia entre WHERE y HAVING en SQL?
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=utf8Con 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 filasObtenemos 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 filasLa 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 > 3descarta grupos ya formados. Solo puede evaluarse cuando elCOUNT()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: rangey 5 filas examinadas. El motor aprovecha el índice para saltar directo al rango que cumple la condición. - Con
HAVING:type: indexy 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 | Sí |
| Acepta funciones agregadas | No | Sí |
Necesita GROUP BY |
No | No (pero es su caso normal) |
| Aprovecha índices para reducir filas | Sí | 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.