Ir al contenido principal

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

Entre las múltiples funciones de agregado que nos ofrece SQL Server, nos encontramos con dos que, probablemente junto con COUNT(), son las más usadas: SUM() y AVG().

El resultado que producen ambas parece bastante obvio: SUM() devuelve la suma de valores de la columna que le indiquemos a la función, mientras que AVG() nos devolverá el valor promedio de los datos de la columna deseada.

Así pues, si tenemos una tabla de pedidos con la columna Importe, que indica el importe final de cada pedido sin decimales (tipo de datos int), si queremos saber la suma de los importes de todos los pedidos, bastará con realizar la siguiente consulta:

SELECT SUM(Importe)
FROM dbo.Pedidos;

Análogamente, si queremos conocer el valor promedio de los pedidos:

SELECT AVG(Importe)
FROM dbo.Pedidos;

El resultado devuelto por ambas será un entero (int) con la suma y el promedio respectivamente.

"Estos números son muy grandes, no sé contar tanto"


Estas funciones probablemente no nos fallarán nunca, hasta que de repente un día veamos un mensaje en rojo que nos diga:

Msg 8115, Level 16, State 2, Line 1
Arithmetic overflow error converting expression to data type int.

¡Oh, oh! ¿Qué ha ocurrido aquí? Muy sencillo: Si el resultado de la suma es más grande que el entero (int) más grande, tenemos un desbordamiento aritmético. El número es demasiado grande como para que SQL Server lo pueda devolver.

Es decir, es como si nos piden sumar 5 y 6 con los dedos de la mano: una vez pasemos de 10, nos hemos quedado sin dedos. Se nos han desbordado las manos.

No podemos contar más de 10 con los dedos de la mano
¿Cuántos son 6 + 5? ¡No tengo suficientes dedos!
Podemos hacer la prueba rápidamente con el siguiente script, en el que introducimos dos pedidos con importe de 1.000.000.000 y 2.000.000.000, y tratamos de obtener la suma de los importes:

CREATE TABLE dbo.Pedidos
(
 NumeroPedido int PRIMARY KEY,
 Importe int
);
INSERT INTO dbo.Pedidos (NumeroPedido, Importe)
VALUES (1, 1000000000), (2, 2000000000);
SELECT SUM(Importe)
FROM dbo.Pedidos;

Ejecutamos el script y se genera el error anteriormente mencionado. La explicación es sencilla: la suma de los importes es 3.000.000.000, pero el valor máximo que puede representar el tipo de datos int es algo más de 2.100.000.000, así que hemos desbordado la suma.

Si ejecutamos el promedio (AVG) ocurrirá exactamente lo mismo:

SELECT AVG(Importe)
FROM dbo.Pedidos;

¿Por qué? Porque primero hay que realizar la suma de todos los importes para después dividir entre el número de elementos. Como la suma desborda el tipo de datos, SQL Server no puede seguir adelante con la operación:

(1.000.000.000 + 2.000.000.000) / 2

Todo tiene solución


En este caso, como muchas otras veces, podemos solucionar este escenario simplemente consultando la ayuda online.

Vemos en la siguiente tabla que SUM devuelve un entero (int) si el tipo de datos de entrada es tinyint (1 byte), smallint (2 bytes) o int (4 bytes), por lo que si intentamos sumar columnas de estos tipos de datos tendremos el límite citado anteriormente (exactamente 2.147.483.647) 
Resultado de la expresión
Tipo de valor devuelto
tinyint
int
smallint
int
int
int
bigint
bigint
Pero si usamos un bigint como tipo de datos de la columna a sumar, el resultado será también un bigint, ampliándonos el límite hasta un valor de 2 elevado a 63 (9.223.372.036.854.775.807). Un número lo suficientemente grande como para que sea complicado de desbordar, aunque no imposible.

Así pues, sin necesidad de cambiar el tipo de datos de nuestras columnas, podemos usar un CAST para convertir los valores de la columna Importe a bigint y después realizar la operación deseada. Del siguiente modo:

SELECT SUM(CAST(Importe AS bigint))
FROM dbo.Pedidos;

SELECT AVG(CAST(Importe AS bigint))
FROM dbo.Pedidos;

¡Ahora SQL Server ya nos devuelve los valores esperados!

Comentarios

  1. interesante articulo te dejo esta entrada que puede complementar este https://thedevelopmentstages.com/having-count-sql-con-ejemplos/

    ResponderEliminar

Publicar un comentario

Entradas populares de este blog

Aprendiendo a usar LEFT OUTER JOIN

En esta entrada pretendemos explicar los diferentes resultados obtenidos por distintas construcciones de consultas que, aparentemente, deberían producir el mismo conjunto de resultados. Así, veremos las diferencias entre filtrar los resultados de una query en la unión (Join) mediante condiciones ON y mediante cláusulas WHERE.

Script para obtener el tamaño de todas las tablas de la base de datos

En algunas ocasiones podemos vernos con la necesidad de conocer qué tablas de nuestra base de datos están ocupando más espacio en disco. Por ejemplo, si disponemos de SQL Server Express , cuyas bases de datos están limitadas a 4GB o 10GB, según la versión que estemos usando -4, hasta 2005; 10, a partir de 2008-, aparte de usar las opciones de comprimir la base de datos, poner el log en el modo simple de recuperación o ajustar las políticas de crecimiento automático de nuestros ficheros, podemos necesitar averiguar qué tablas crecen más para tomar las decisiones oportunas.

Cómo averiguar el propietario (Owner) de una base de datos en SQL Server

En ocasiones necesitamos conocer quién es el propietario de una base de datos . Normalmente podemos acceder a esta información a través del SQL Server Management Studio , consultando las propiedades de la base de datos. Para ello, buscamos la base de datos que queramos consultar en el árbol de bases de datos del Object Explorer y hacemos clic derecho sobre ella. A continuación, elegimos la última opción del menú contextual que nos aparece: Propiedades . Veremos esta ventana: Propiedades de una base de datos, que incluyen el propietario (en amarillo) Sin embargo, en ocasiones no tenemos acceso a esta ventana de propiedades o, simplemente, queremos consultar el propietario de todas las bases de datos de un servidor. Para ello disponemos del siguiente script T-SQL , que nos proporcionará la información deseada: La primera columna nos dirá el propietario de la base de datos (en concreto, su login o nombre de usuario). A partir de este script , podemos filtrar por base de dat...