La cláusula HAVING: filtrando las filas agrupadas

Deja un comentario

En artículos anteriores ya hemos visto a la cláusula GROUP BY, la forma de usarla, y su utilidad práctica. Así que ahora nos preguntamos ¿y si solamente nos interesan algunas de esas filas agrupadas?

Pues en nuestro socorro viene la cláusula HAVING.

Después de haber agrupado a las filas usando la cláusula GROUP BY, podemos establecer las condiciones que esas filas agrupadas deben cumplir.

Esto es algo opcional, no siempre se requiere filtrar a las filas agrupadas.

Veamos un ejemplo:

GROUP1

Captura 1. Si haces clic en la imagen la verás más grande

En la tabla MOVIMCAB (cabecera de movimientos) tenemos los datos de cada venta realizada. Queremos agrupar a esas filas por identificador del cliente, para ver el total vendido a cada cliente.

Listado 1.

SELECT
   MVC_IDECLI,
   SUM(MVC_TOTALX) AS TOTAL_VENTA
FROM
   MOVIMCAB
GROUP BY
   MVC_IDECLI

GROUP3

Captura 2. Si haces clic en la imagen la verás más grande

En el Listado 1. tenemos la solución, después de ejecutarlo veremos la Captura 2. Todo bien hasta ahí. Pero supongamos que ahora solamente nos interesa ver los clientes a quienes les hemos vendido por 1.000.000 ó más.

Listado 2.

SELECT
   MVC_IDECLI,
   SUM(MVC_TOTALX) AS TOTAL_VENTA
FROM
   MOVIMCAB
GROUP BY
   MVC_IDECLI
HAVING
   SUM(MVC_TOTALX) >= 1000000

GROUP7

Captura 3. Si haces clic en la imagen la verás más grande

En el Listado 2. hemos usado a la cláusula HAVING y el resultado obtenido lo vemos en la Captura 3.

¿Qué hace la cláusula HAVING?

Filtra (o sea, le pone condiciones) a las filas que estarán en el conjunto resultado de nuestra consulta. La cláusula HAVING solamente puede usarse después que la cláusula GROUP BY y solamente allí, en ningún otro lugar es admitida.

En la Captura 2. hemos visto el resultado de agrupar a las ventas por clientes, en la Captura 3. vemos el resultado de poner una condición a las filas de la Captura 2.

Es decir que tenemos a las filas que vemos en la Captura 2. pero solamente algunas de esas filas nos interesan. Allí es donde usamos a la cláusula HAVING, para que solamente las filas de la Captura 2. que cumplen nuestra condición sean mostradas.

La cláusula HAVING es muy similar a la cláusula WHERE, pero mientras que WHERE filtra a todas las filas, HAVING solamente filtra a las filas que obtuvimos después de un agrupamiento.

A veces, podemos usar WHERE o podemos usar HAVING, y el resultado obtenido es el mismo. A veces solamente podemos usar a HAVING, no está permitido usar WHERE. Veamos ejemplos:

Con WHERE y con HAVING se obtiene el mismo resultado

Listado 3.

SELECT
   MVC_IDECLI,
   SUM(MVC_TOTALX) AS TOTAL_VENTA
FROM
   MOVIMCAB
WHERE
   MVC_IDECLI >= 5
GROUP BY
   MVC_IDECLI

Listado 4.

SELECT
   MVC_IDECLI,
   SUM(MVC_TOTALX) AS TOTAL_VENTA
FROM
   MOVIMCAB
GROUP BY
   MVC_IDECLI
HAVING
   MVC_IDECLI >= 5

GROUP8

Captura 4. Si haces clic en la imagen la verás más grande

Tanto si ejecutamos el Listado 3. o el Listado 4. el resultado que obtendremos será el mismo: el mostrado en la Captura 4.

¿Por qué?

Porque la condición de filtro fue puesta en la columna MVC_IDECLI, o sea en una columna que no es una función agregada: COUNT(), SUM(), MAX(), MIN(), AVG().

En cambio, si la condición la ponemos en una columna que proviene de una función agregada, solamente podremos usar la cláusula HAVING, no podemos usar una función agregada en la cláusula WHERE.

Listado 5.

SELECT
   MVC_IDECLI,
   SUM(MVC_TOTALX) AS TOTAL_VENTA
FROM
   MOVIMCAB
WHERE
   SUM(MVC_TOTALX) >= 1000000
GROUP BY
   MVC_IDECLI

El Listado 5. es incorrecto, el Firebird no lo aceptará y nos mostrará el mensaje: “Cannot use an aggregate function in a WHERE clause, use HAVING instead.

Por lo tanto, si queremos filtrar a las funciones agregadas no podremos escribirlas en la cláusula WHERE, solamente podremos escribirlas en la cláusula HAVING.

En síntesis, si tu condición de filtro no involucra a una función agregada puedes usar la cláusula WHERE o la cláusula HAVING para filtrar a esas filas, como prefieras, es tu elección, al Firebird le da lo mismo. Pero si tu condición de filtro involucra a una función agregada solamente puedes usar a la cláusula HAVING, no tienes otra posibilidad.

No se pueden usar alias en la cláusula HAVING

Listado 6.

SELECT
   MVC_IDECLI,
   SUM(MVC_TOTALX) AS TOTAL_VENTA
FROM
   MOVIMCAB
GROUP BY
   MVC_IDECLI
HAVING
   TOTAL_VENTA >= 1000000

En el Listado 6. hay un error y el Firebird nos avisará con el mensaje: “Column unknown. TOTAL_VENTA.

¿Cuál es el error? Que quisimos usar al alias de una columna (en este caso el alias es TOTAL_VENTA) en la cláusula HAVING y eso no está permitido. La forma correcta es la mostrada en el Listado 2., que sí funcionará perfectamente.

Conclusión:

Si después de agrupar a las filas nos interesa ver solamente a algunas de ellas, entonces podemos establecer la condición (o condiciones) que deben cumplir esas filas en la cláusula HAVING.

La cláusula HAVING solamente puede escribirse inmediatamente después que la cláusula GROUP BY, en ningún otro lugar es admitida.

A veces podemos filtrar a las filas usando la cláusula WHERE o usando la cláusula HAVING, indistintamente. Eso ocurre cuando nuestro filtro no involucra a una función agregada. En tales casos ya es preferencia de cada uno usar WHERE o usar HAVING.

Sin embargo, si la condición de filtro involucra a una (o más de una) función/es agregada/s entonces solamente podemos usar la cláusula HAVING, usar la cláusula WHERE no está permitido en esos casos.

Otro punto importante a recordar es que los alias de las columnas no pueden usarse con la cláusula HAVING.

Artículos relacionados:

Entendiendo la cláusula GROUP BY: agrupando datos

La cláusula GROUP BY requiere estar ordenada

El índice del blog Firebird21

El foro del blog Firebird21

Anuncios

Agrupar y ordenar por nombres o por posiciones de las columnas

Deja un comentario

Como sabes, en SQL para ordenar el resultado de una consulta puedes escribir:

SELECT
   PRD_CODIGO,
   PRD_NOMBRE
FROM
   PRODUCTOS
ORDER BY
   PRD_NOMBRE

o escribir algo como:

SELECT
   PRD_CODIGO,
   PRD_NOMBRE
FROM
   PRODUCTOS
ORDER BY
   2

Y el resultado que obtendrás será exactamente el mismo. De la misma manera puedes agrupar por nombres de las columnas o por la posición que esas columnas ocupan.

¿Qué es preferible, usar el nombre de la columna o la posición de la columna?

Antes de que existiera SQL la forma normal era utilizar la posición de la columna, el hecho de poder utilizar el nombre de la columna en SQL resultó un gran avance en esa época.

Si usas la posición, el problema que puedes tener es que agregas una columna a tu SELECT y si te olvidas de actualizar también las cláusulas ORDER BY o GROUP BY, entonces obtendrás un resultado incorrecto.

SELECT
   PRD_CODIGO,
   PRD_UNIMED,
   PRD_NOMBRE
FROM
   PRODUCTOS
ORDER BY
   2

Aquí, se agregó la columna PRD_UNIMED (unidad de medida) pero no se actualizó la cláusula ORDER BY y por lo tanto el orden de las filas no será el correcto. En cambio, en este caso:

SELECT
   PRD_CODIGO,
   PRD_UNIMED,
   PRD_NOMBRE
FROM
   PRODUCTOS
ORDER BY
   PRD_NOMBRE

También se agregó la columna PRD_UNIMED pero sin embargo se sigue mostrando a las filas en el orden correcto porque en la cláusula ORDER BY se utiliza el nombre de la columna, no su posición.

Conclusión:

Usar el nombre de la columna o la posición de la columna tiene ventajas y desventajas.

La ventaja de usar el nombre de la columna es que aunque agregues columnas el resultado siempre estará ordenado o agrupado correctamente.

La ventaja de usar la posición de la columna es que escribes menos.

Los buenos profesionales siempre recomiendan usar el nombre de la columna porque se escribe más, sí, pero se entiende mejor.

Artículo relacionado:

El índice del blog Firebird21

 

La función MAX()

2 comentarios

Esta función agregada nos mostrará:

  • Si la columna es numérica, el número mayor de todos
  • Si la columna es carácter, la palabra que en orden alfabético esté última
  • Si la columna es fecha, la que tiene la fecha más reciente

No tiene en cuenta a los NULL, simplemente los ignora y halla el valor máximo de las demás columnas.

SELECT
   CLI_CODIGO,
   MAX(CLI_MONTOX) AS MAYOR_MONTO_COBRADO
FROM
   COBRANZAS
GROUP BY
   CLI_CODIGO

El SELECT anterior nos mostrará, para cada uno de nuestros clientes, el mayor monto de todos los que le hemos cobrado hasta ahora. El alias “MAYOR_MONTO_COBRADO” es opcional, no es necesario escribir un alias pero sí es conveniente para que sea más entendible.

Como todas las funciones agregadas, MAX() se usa casi siempre con la cláusula GROUP BY.