En MySQL – Diferencia entre WHERE y HAVING
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;
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.