8 - Filtros HAVING

Aprende a filtrar grupos de datos basándote en los resultados de tus funciones de agregado


Si se plantea la siguiente cuestión:

¿Qué ubicaciones del centro de mascotas tienen más de dos ejemplares?

Usted podría responder a la tercera pregunta de construcción: ¿Qué requisitos deben cumplir los registros?, lo siguiente: "que la ubicación tenga más de dos ejemplares"; y esa respuesta sería errónea.

Esta pregunta nos la formulamos para construir la cláusula WHERE y aplicar filtros a los registros de la tabla, pero como el número de ejemplares de cada ubicación no lo tenemos en ninguna tabla, sino que debemos calcularlo, no podemos aplicar ese filtro en la cláusula WHERE. ¿Dónde se aplica entonces?

En estos casos debemos filtrar las filas de resultados, es decir, de todas las filas resultantes ocultar las que no nos interesen y mostrar el resto. Puede verse como un filtro en segunda instancia, una vez el motor ha resuelto la consulta y siempre de forma ajena a la tabla de datos. Para ello existe una nueva cláusula: HAVING, y en consecuencia una nueva pregunta de construcción: ¿qué requisitos deben cumplir los datos totalizados?


Cláusula HAVING

¿Qué ubicaciones del centro de mascotas tienen más de dos ejemplares?

Construyamos la consulta SQL que resuelve la cuestión planteada con ayuda de las preguntas de construcción:

  • ¿Qué datos nos piden?
    Las ubicaciones.
  • ¿Dónde están los datos?
    En la tabla mascotas.
  • ¿Qué requisitos deben cumplir los registros?
    Ubicaciones que contengan mascotas de alta en el centro.
  • ¿Cómo debemos agrupar los datos?
    Por ubicación.
  • ¿Qué requisito han de cumplir los datos totalizados?
    Que el número de ejemplares de las ubicaciones sea mayor a dos.

Consulta SQL:


  select UBICACION, count(*) as EJEMPLARES
    from MASCOTAS
   where ESTADO = 'A'
group by UBICACION
  having count(*) > 2
            

Resultado:

UBICACIONEJEMPLARES
E024
E043

Motores como MariaDB permiten usar el alias de columna para filtrar las filas de resultado:


  select UBICACION, count(*) as EJEMPLARES
    from MASCOTAS
   where ESTADO = 'A'
group by UBICACION
  having EJEMPLARES > 2
                

El estándar ANSI SQL no contempla este uso. En motores como Oracle, SQL Server o PostgreSQL, no se permite el uso de alias dentro del HAVING. Esto se debe principalmente al orden lógico de ejecución de la consulta. Cuando el motor está resolviendo el HAVING, aún no ha tratado el SELECT. Veremos las fases lógicas de ejecución en el resumen de la primera parte del curso, en la lección 11. Para asegurar la compatibilidad del código, lo más recomendable es repetir la expresión de la cláusula SELECT, en este caso, COUNT(*), al filtrar las filas de resultado.

De las cinco ubicaciones que existen en el centro, solo dos cumplen la condición de la cláusula HAVING. Esta cláusula es, de hecho, como la cláusula WHERE, pero en lugar de filtrar registros de la tabla, filtra filas de resultado en función de las condiciones que establezcamos sobre las columnas de resultado. En realidad este recurso se usa principalmente para establecer condiciones sobre las funciones de agregado, puesto que los demás valores, los que están en la tabla, los debemos filtrar en la cláusula WHERE.

A fin de ser prácticos consideraremos la cláusula HAVING como una cláusula WHERE para los cálculos de totalización. De modo que lo que filtraremos aquí serán cosas del estilo: que la suma sea inferior a..., que la media sea igual a..., que el máximo sea superior a..., o como en el ejemplo: que el recuento de registros sea superior a dos. Siempre sobre cálculos de totalización. Por lo tanto si no hay cláusula GROUP BY, tampoco habrá cláusula HAVING.


Diferencia entre WHERE y HAVING

Veamos con más detalle la diferencia entre una y otra cláusula. Cuando el motor SQL recorre la tabla para obtener el resultado, ignora los registros que no satisfacen la cláusula WHERE, en el caso anterior ignora los registros cuyo campo ESTADO no contenga una 'A', y esos registros no son considerados para desarrollar el cálculo.

Una vez el motor SQL ha recorrido toda la tabla y ha finalizado el cálculo, de las filas resultantes ocultará las que no satisfacen la cláusula HAVING, por lo que en primer lugar: no se ahorra hacer el cálculo para las filas de resultados no mostradas (de lo contrario no podría saber si cumplen o no la condición de la cláusula HAVING), y en segundo lugar, este filtro se aplica en la fase final del proceso que ejecuta el motor SQL, siempre sobre las filas de resultados, escrutando los datos totalizados (COUNT, SUM, MAX...), limitándose a mostrar o no una fila de resultado en función de las condiciones establecidas en dicha cláusula.

Todo lo expuesto sobre lógica booleana en la lección 5 es aplicable a la cláusula HAVING, teniendo en cuenta que lo correcto es establecer condiciones sobre las funciones de agregado, y carece de sentido establecer condiciones que podríamos perfectamente establecer en la cláusula WHERE, puesto que en ese caso estaremos haciendo trabajar al motor SQL en vano, es decir, le estaremos obligando a considerar registros que se podría ahorrar ya que finalmente se ocultará la fila o cálculo referente a ese grupo de registros. Por ejemplo:

La siguiente consulta SQL cuenta los ejemplares de alta de las ubicaciones E02 y E03:


  select UBICACION , count(*) as EJEMPLARES
    from MASCOTAS
   where ESTADO = 'A' and (UBICACION = 'E02' or UBICACION = 'E03')
group by UBICACION
            

Pero esta otra consulta SQL cuenta los ejemplares de alta en todas las ubicaciones y finalmente oculta los que la ubicación no es E02 o E03. Por lo que este método no es eficiente:


  select UBICACION , count(*) as EJEMPLARES
    from MASCOTAS
   where ESTADO = 'A'
group by UBICACION
  having UBICACION = 'E02' or UBICACION = 'E03'
            

El resultado de ambas consultas SQL es el mismo, pero hacer lo segundo es no entender el propósito de cada cláusula. Para no caer en este error basta con filtrar siempre las filas de resultado condicionando únicamente funciones de agregado en la cláusula HAVING.


Veamos otro ejemplo antes de pasar a los ejercicios:

¿Qué ubicaciones del centro de mascotas tienen tan solo un ejemplar?

La consulta que resuelve esta cuestión es casi idéntica a la primera consulta de esta lección. Ahora en lugar de condicionar el número de ejemplares mayor a dos, tan solo debe haber un ejemplar.

Consulta SQL:


  select UBICACION , count(*) as EJEMPLARES
    from MASCOTAS  
   where ESTADO = 'A'
group by UBICACION
  having count(*) = 1
            

Resultado:

UBICACIONEJEMPLARES
E031

De hecho, como en este caso estamos forzando a que solo haya un ejemplar, podríamos ocultar la columna de recuento omitiéndola en la cláusula SELECT del siguiente modo:

Consulta SQL:


  select UBICACION as "UBICACIONES CON UN EJEMPLAR"
    from MASCOTAS
   where ESTADO = 'A'
group by UBICACION
  having count(*) = 1
            

Resultado:

UBICACIONES CON UN EJEMPLAR
E03

Resumen

La cláusula HAVING permite establecer filtros sobre los cálculos de una consulta SQL que realizan las funciones de agregado (SUM, COUNT, etc...).

En la cláusula HAVING solo deben condicionarse columnas de cálculo, de modo que si en una consulta SQL no existe la cláusula GROUP BY, tampoco existirá cláusula HAVING.


Ejercicio Propuesto

Usando el operador BETWEEN que vimos en las lecciones 3 y 5, construya una consulta que devuelva las ubicaciones del centro de mascotas que tienen entre 2 y 3 ejemplares.

Ir a la Consola

Reseñas de la lección 8

Aún no hay reseñas para esta lección. ¡Sé el primero!