En MySQL – Diferencia entre WHERE y HAVING

SQL

No había encontrado ni en inglés ni en español una explicación rápida y clara de las diferencias. Así que voy a tratar de explicar y ejemplificar lo mas sencillo posible.

Hace unos días tuve una importante entrevista de trabajo, todo iba normal, hasta que me preguntaron la diferencia entre WHERE y HAVING. Lo primero que se me vino a la mente fue lo que recordé de las lecturas; “HAVING va acompañado de la declaración GROUP BY“. Así que después de analizar un poco más a detalle he encontrado las siguientes diferencias. Comenzamos.


Primero vamos a crear una tabla:

CREATE TABLE IF NOT EXISTS minutos_usuario (
 id INT NOT NULL AUTO_INCREMENT,
 id_usuario CHAR(9) NOT NULL,
 nombre VARCHAR(40) NOT NULL,
 genero VARCHAR(20) DEFAULT 'Desconocido',
 minutos_visto INT(10) NOT NULL,
 fecha DATE,
 PRIMARY KEY (id)
 KEY minutos_visto (minutos_visto)
 );

Después vamos a insertar información a la tabla solo para el ejemplo:

INSERT INTO minutos_usuario (id_usuario, nombre, genero, minutos_visto, fecha) VALUES
('USR_MX_02', 'Django', 'Wester', 165, '2022-01-05'),
('USR_MX_01', 'The Hunger Games', 'Fiction', 142, '2021-12-20'),
('USR_MX_05', 'The Mitchells vs. the Machines', 'Comedy', 114, '2021-12-19'),
('USR_MX_02', 'The Power of the Dog', 'Wester', 125, '2021-12-18'),
('USR_MX_04', 'The Lost Daughter', 'Drama', 124, '2021-12-15'),
('USR_MX_02', 'The White Tiger', 'Novel', 125, '2021-12-10'),
('USR_MX_03', 'The Power of the Dog', 'Wester', 125, '2021-12-08'),
('USR_MX_06', 'The Mitchells vs. the Machines', 'Comedy', 114, '2021-12-08'),
('USR_MX_05', 'Pasing', 'Novel', 99, '2021-12-07'),
('USR_MX_04', 'Fear Street Part Three: 1666', 'Supernatural', 114, '2021-12-05'),
('USR_MX_04', 'The Royal Treatment', 'Romance', 97, '2021-11-28'),
('USR_MX_03', 'The Mitchells vs. the Machines', 'Comedy', 114, '2021-11-26'),
('USR_MX_05', 'Bruised', 'Drama', 138, '2021-11-20'),
('USR_MX_07', 'The Lost Daughter', 'Drama', 124, '2021-11-15'),
('USR_MX_01', 'The Power of the Dog', 'Wester', 125, '2021-11-08'), 
('USR_MX_03', 'The Power of the Dog', 'Wester', 125, '2021-11-01'),
('USR_MX_02', 'The White Tiger', 'Novel', 125, '2021-10-29'),
('USR_MX_10', 'The Mitchells vs. the Machines', 'Comedy', 114, '2021-10-28'),
('USR_MX_01', 'The Royal Treatment', 'Romance', 97, '2021-10-18'),
('USR_MX_06', 'The Power of the Dog', 'Wester', 125, '2021-10-08'),
('USR_MX_03', 'Bruised', 'Drama', 138, '2021-10-25'),
('USR_MX_06', 'The Lost Daughter', 'Drama', 124, '2021-11-15'),
('USR_MX_02', 'The Hunger Games', 'Fiction', 142, '2021-12-20'),
('USR_MX_08', 'The Mitchells vs. the Machines', 'Comedy', 114, '2021-12-19'),
('USR_MX_09', 'Django', 'Wester', 165, '2021-11-05'),
('USR_MX_04', 'Pasing', 'Novel', 99, '2021-12-07'),
('USR_MX_05', 'Pasing', 'Novel', 99, '2021-11-07'),
('USR_MX_08', 'Django', 'Wester', 165, '2021-12-05'),
('USR_MX_01', 'The Power of the Dog', 'Wester', 125, '2021-12-18'),
('USR_MX_09', 'Django', 'Wester', 165, '2021-10-15');

Ahora vamos a ver una de las principales diferencias. Si ejecutamos:

SELECT
	id_usuario,
	genero,
	minutos_visto AS total 
FROM
	minutos_usuario 
WHERE
	total > 160;

Vamos a obtener:

> 1054 - Unknown column 'total' in 'where clause'

En cambio, si ejecutamos:

SELECT
	id_usuario,
	genero,
	minutos_visto AS total 
FROM
	minutos_usuario 
HAVING
	total > 160

Obtendremos el siguiente resultado:

Como podemos ver la primera diferencia es que WHERE trabaja con una condición sobre una columna y no permite ALIAS y mas adelante veremos como tampoco admite funciones, por otro lado vemos que HAVING permite trabajar una columna, con ALIAS y mas adelante veremos que también trabaja con funciones.


Siguiendo con otro ejemplo buscamos obtener el mismo resultado en dos consultas, una utilizando WHERE y en la segunda utilizando HAVING:

SELECT
	id_usuario,
	genero,
	minutos_visto 
FROM
	minutos_usuario 
WHERE minutos_visto < 100
ORDER BY id_usuario;

/*---------------------------*/

SELECT
	id_usuario,
	genero,
	minutos_visto 
FROM
	minutos_usuario 
HAVING minutos_visto < 100
ORDER BY id_usuario;

Obtenemos el mismo resultado, pero no hemos utilizado ninguna función, simplemente solicitamos que nos trajera los usuarios donde los minutos vistos fueran menores a 100, ordenado por usuario.

(WHERE-Izquierda – HAVING-Derecha.)

Aparentemente vemos que el resultado es el mismo, y sí lo es, solamente observemos una diferencia, el tiempo que tomó ejecutar el Query con WHERE fueron 0.020s y con HAVING 0.023s. Posiblemente sea causado por que la clausula WHERE filtra la información antes de ejecutar el SELECT y en el caso de HAVING filtra los resultados después del SELECT.

Vamos a ejecutar el mismo código empleando EXPLAIN para observar más detalles:

EXPLAIN SELECT
	id_usuario,
	genero,
	minutos_visto 
FROM
	minutos_usuario 
WHERE minutos_visto < 100;

/*---------------------------*/

EXPLAIN SELECT
	id_usuario,
	genero,
	minutos_visto 
FROM
	minutos_usuario 
HAVING minutos_visto < 100;
Consulta con WHERE
Consulta con HAVING

Vemos las principales diferencias:

WHERE es de tipo RANGE, trabaja con KEY, opera con 01 ROW y el Query es mas rápido con 0.020s.

HAVING es de tipo ALL, no trabaja con KEY, opera con 30 ROW y el Query es más lento con 0.022s.


Ahora dos consultas, en la primera solamente vamos a utilizar HAVING y en la segunda consulta vamos a combinar HAVING y a agregar WHERE :

SELECT
	id_usuario,
	genero,
	sum( minutos_visto ) AS total 
FROM
	minutos_usuario 
GROUP BY
	id_usuario,
	genero 
HAVING
	sum( minutos_visto ) > 200

/*----------------------------------*/

SELECT
	id_usuario,
	genero,
	sum( minutos_visto ) AS total 
FROM
	minutos_usuario 
WHERE
	genero <> 'Novel' 
GROUP BY
	id_usuario,
	genero 
HAVING
	sum( minutos_visto ) > 200;

En el Resultado 1 le estamos pidiendo nos traiga todos los usuarios que han visto mas de 200 minutos de cada género. Y en el Resultado 2 le pedimos lo mismo solamente excluyendo el género Novel.

Como vemos se pueden combinar sin mayor problema.


Vamos a ver varios ejemplos de consultas con HAVING.

Primero vamos a utilizar la función de promedio (AVG).

SELECT
	genero,
	AVG( minutos_visto ) AS promedio 
FROM
	minutos_usuario 
GROUP BY
	genero
HAVING
	promedio > 100
ORDER BY
	genero;

Donde solamente nos muestra el promedio de minutos que han sido vistos en cada género, mayor a 100 minutos, sin importar el usuario y el tiempo.

Ahora empleamos la función para sumar (SUM)

SELECT
	genero,
	SUM( minutos_visto ) AS suma
FROM
	minutos_usuario 
GROUP BY
	genero
HAVING
	suma> 100
ORDER BY
	suma;

Lo único que hicimos fue sumar los minutos vistos de cada género y mostrar los mayores a 100, sin importar usuarios y tiempo.


Por ultimo vamos a ver algo mas complejo.

Queremos a todos los usuarios que han visto más de 100 minutos en los últimos 2 meses, clasificados por usuario y después por género y que sea ordenado por género y por minutos vistos. (NOTA: Si estas observando esta publicación un mes después de que se publicó, debes ajustar el intervalo de meses para poder observar resultados)

SELECT
	id_usuario,
	genero,
	SUM(minutos_visto) AS total
FROM
	minutos_usuario 
WHERE
	fecha >= DATE_SUB(NOW(), INTERVAL 2 MONTH) 
GROUP BY id_usuario, genero
HAVING total > 100
ORDER BY genero, total DESC;

En conclusión diremos que podemos obtener resultados distintos y resultados semejantes dependiendo de la consulta, pero aún cuando los resultados se asemejan tienen distinto comportamiento.

Si pudiera regresar el tiempo y responder esa pregunta, diría que la diferencia entre WHERE y HAVING es que WHERE apunta a una sola columna y no permite ser utilizada en funciones, mientras que HAVING permite ser utilizada sobre una columna y en funciones y hacer referencia a alias.

Si tuviera que traducir WHERE diría que es: “En donde X se compare con Y y si tuviera que traducir HAVING diría “En donde se tenga X dentro del grupo Y.

Espero que haya podido explicar. Si tienen alguna duda o sugerencia por favor déjame un comentario.

About: Pablo


    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Share via
    Copy link