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

Poniendo los JOIN en el orden correcto

1 comentario

Muchas veces cuando escribimos un SELECT que no es muy sencillo necesitamos relacionar a una tabla con otra u otras tablas. Eso lo hacemos con las cláusulas JOIN.

Como sabemos, hay dos clases de JOIN:

  • INNER JOIN
  • OUTER JOIN

La palabra INNER puede omitirse si se desea, ya que es la que por defecto asumirá el Firebird si no se escribe algo antes de la palabra JOIN. Los OUTER a su vez pueden ser: LEFT, RIGHT, FULL

Entonces, la pregunta es: ¿importa si se escribe primero un INNER o un OUTER? ¿O es lo mismo?

Consulta 1. Poniendo primero el INNER JOIN

SELECT
   *
FROM
   MiTabla1
JOIN
   MiTabla2
      ON MiCondición1
LEFT JOIN
   MiTabla3
      ON MiCondición2

Aquí se escribió primero el INNER JOIN (la palabra INNER no es obligatorio escribir) y luego el OUTER JOIN (la palabra OUTER tampoco es obligatorio escribir, al escribir LEFT ya el Firebird sabe que se trata de un OUTER)

Consulta 2. Poniendo primero el OUTER JOIN

SELECT
   *
FROM
   MiTabla1
LEFT JOIN
   MiTabla3
      ON MiCondición2
JOIN
   MiTabla2
      ON MiCondición1

Aquí se escribió primero el OUTER JOIN (en este caso, fue un LEFT) y luego el INNER JOIN (la palabra INNER es opcional, no se requiere escribirla)

¿Cuál consulta es mejor?

¿La Consulta 1 es más eficiente que la Consulta 2? ¿La Consulta 2 es más eficiente que la Consulta 1? ¿O son igual de eficientes?

El optimizador del Firebird solamente reordena las tablas en el PLAN hasta que encuentra el primer OUTER JOIN (sea LEFT, RIGHT, o FULL).

Por lo tanto, la correcta es la Consulta 1.

Conclusión:

El orden de los JOIN sí importa en Firebird porque el optimizador reordena las tablas para obtener un mejor PLAN solamente hasta que encuentra el primer OUTER JOIN.

¿Consejo?

Revisa todos tus SELECTs y todas tus vistas para asegurarte de que siempre los INNER JOIN se encuentren antes de los OUTER JOIN, conseguirás consultas más eficientes de esa manera.

Artículos relacionados:

INNER JOIN y OUTER JOIN

Ejemplo Nº 001 – Usando INNER JOIN

Ejemplo Nº 002 – Usando INNER JOIN eficientemente

Ejemplo Nº 003 – Escribiendo varios INNER JOIN

Ejemplo Nº 004 – Usando un INNER JOIN para autoreferenciar una tabla

Ejemplo Nº 006 – Usando LEFT JOIN e INNER JOIN

JOIN implícito y JOIN explícito

NATURAL JOIN y CROSS JOIN

Relacionando dos tablas: la forma vieja y la forma nueva

Teoría de Conjuntos: Unión, Intersección, Diferencia

Entendiendo las tablas autoreferenciadas

Autoreferenciar una tabla. Algunos ejemplos

El índice del blog Firebird21

El foro del blog Firebird21

Ejemplo Nº 052 – Comparando strings

Deja un comentario

Cuando comparamos strings el resultado puede no ser el que esperábamos si es que no lo hacemos bien.

Ejemplo:

SELECT
   1
FROM
   RDB$DATABASE
WHERE
   'A' = 'A      '

Este SELECT devolverá 1 aunque los strings son diferentes. ¿Es eso correcto o incorrecto? Pues es lo correcto según el estándar SQL el cual dice lo siguiente: “cuando se comparan strings de distinta longitud, la comparación debe ser hecha como si al string más corto se le agregaran espacios en blanco hasta la longitud del string más largo”.

¿Y si queremos que la condición no se cumpla cuándo las longitudes son diferentes?

En ese caso deberemos utilizar LIKE, como vemos a continuación:

SELECT
   1
FROM
   RDB$DATABASE
WHERE
   'A' LIKE 'A   '

Aquí el resultado de la consulta será un conjunto vacío porque la condición no se cumple.

Conclusión:

Si quieres que la comparación entre dos strings sea estricta (o sea que sean idénticos y que tengan la misma longitud) debes usar el operador LIKE, no el símbolo =

Artículo relacionado:

El índice del blog Firebird21