Desbordando AVG y SUM: Cuando SQL Server se queda sin dedos para sumar

ISNULL y MAX, ¡cuidado! El orden importa

Imaginemos que queremos obtener el valor máximo de una columna de una tabla que puede contener valores nulos y que, en caso de no existir ningún valor en ella o ser todos los existentes nulos, deseamos obtener un cero.

T-SQL nos ofrece los operadores MAX e ISNULL para conseguir tal propósito, pero debemos tener cuidado con el orden en que los usamos, pues si la consulta SELECT que estamos ejecutando no devuelve ningún registro, el resultado podría no ser el que esperamos.

MAX, como fácilmente se puede deducir de su nombre, nos devolverá el máximo valor de la expresión que indiquemos entre paréntesis. En nuestro caso, un nombre de columna. Se ignorarán los valores NULL.

Como, en caso de no existir ningún valor en la columna, queremos que nos devuelva un cero, usaremos la función ISNULL.

ISNULL sustituye los nulos del campo indicado por el valor indicado tras la coma.

Enunciado

Así, supongamos que tenemos una tabla de contactos, y queremos extraer de ella la máxima edad de nuestros contactos de Italia. Age es la columna que contiene la edad, admite valores nulos, ya que no podemos disponer de ese dato para  todos nuestros contactos.

Como queremos eliminar los valores nulos, usaremos ISNULL para reemplazarlos por un cero. Una vez hayamos hecho esto, obtendremos el máximo valor con MAX. Fácil, ¿no? La consulta quedaría como sigue:

SELECT MAX(ISNULL(Age, 0))
FROM Contact
WHERE Country = N'Italy'

La consulta funcionará correctamente... ¡siempre que tengamos algún contacto cuyo país sea Italia!

Sí, por ejemplo, tenemos la siguiente distribución de datos:


ContactId   Country    Age
----------- ---------- ------
1               Italy          57
2               Italy          NULL

Obtendremos un 57, tras haber convertido el NULL en un cero.

Si solamente existiese el segundo registro con país igual a Italia, obtendríamos un cero, procedente del NULL.

Pero, ¿qué pasaría si no tuviésemos ningún registro cuyo país es Italia? Para nuestra sorpresa, el resultado de la consulta sería ¡NULL!


Explicación del error

MAX siempre devuelve un valor del tipo de datos de la expresión que queremos evaluar. Si la expresión no contiene nada que evaluar, MAX nos devuelve un bonito nulo. Así de simple.

El ISNULL que estamos aplicando a la expresión -en nuestro caso, la columna Age de la tabla Contact- no puede hacer nada, ya que no existen NULL en esa columna que transformar en cero. Es una método de transformación, de reemplazo. Si la consulta original no devuelve registros, ISNULL no hará que aparezcan.

Por ejemplo, podéis comprobarlo fácilmente con la siguiente consulta:

SELECT ISNULL(Age, 0)
FROM Contact
WHERE 1 = 0

Veréis como SQL Server os devuelve: (0 row(s) affected)

Solución

Evitar la aparición de estos inesperados valores NULL por sorpresa es sencillo. Sólo tenemos que invertir el orden en el que aplicamos ISNULL y MAX.

Sabemos que MAX siempre nos va a generar un resultado que puede ser desde el valor que buscamos hasta un NULL en caso de que no existan registros o que todos los registros de la expresión sean, a su vez, nulos. Así pues, será la primera operación que apliquemos.

Una vez hayamos obtenido un único valor con MAX, para asegurarnos de que no devolvemos un valor nulo, es el turno de usar ISNULL, y decimos que, en ese caso, queremos que la consulta nos devuelva un cero.

Esta sería, pues, la consulta correcta:

SELECT ISNULL(MAX(Age), 0)
FROM Contact
WHERE Country = N'Italy'

Es sencillo, pero también es fácil equivocarse y escribirla en el orden incorrecto, lo que puede hacer a nuestra consulta devolver un inesperado e incómodo NULL en el momento menos oportuno.

Comentarios

  1. Estaba buscando algo sobre Transact y me aclaró el concepto, Gracias

    ResponderEliminar
  2. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  3. hola quisiera saber quien me puede ayudar lo que pasa es que tengo una tabla de precios y tengo que sacar el MAX pero la misma tabla pero tambien ocupo hacer un select * al mismo campo , quien me ayudaria , Saludos

    ResponderEliminar

Publicar un comentario