¿Cómo evitar el error "dividir por cero" en SQL?


363

Tengo este mensaje de error:

Mensaje 8134, Nivel 16, Estado 1, Línea 1 Divide por cero error encontrado.

¿Cuál es la mejor manera de escribir código SQL para que nunca vuelva a ver este mensaje de error?

Podría hacer cualquiera de los siguientes:

  • Agregue una cláusula where para que mi divisor nunca sea cero

O

  • Podría agregar una declaración de caso, para que haya un tratamiento especial para cero.

¿Es la mejor manera de usar una NULLIFcláusula?

¿Hay una mejor manera, o cómo se puede hacer cumplir?


77
Quizás alguna validación de datos esté en orden.
Anthony

Respuestas:


645

Para evitar un error de "División por cero", lo hemos programado así:

Select Case when divisor=0 then null
Else dividend / divisor
End ,,,

Pero aquí hay una manera mucho mejor de hacerlo:

Select dividend / NULLIF(divisor, 0) ...

Ahora el único problema es recordar el bit NullIf, si uso la tecla "/".


13
Una forma mucho mejor de hacerlo "Seleccionar dividendo / nulo (divisor, 0) ..." se rompe si el divisor es NULO.
Anderson

8
@ Anderson Eso no es cierto en absoluto. ¿Estás seguro de que no usaste accidentalmente en IsNulllugar de NullIf? ¡Inténtalo tú mismo! SELECT Value,1/NullIf(Value,0)FROM(VALUES(0),(5.0),(NULL))x(Value);¿A menos que por "interrupciones" se refiera a un NULL? Puedes convertir eso a lo que quieras con IsNullo Coalesce.
ErikE

1
@ErikE, es cierto ... intente ejecutar ... seleccione 1 / nullif (null, 0) ... obtendrá "El tipo del primer argumento para NULLIF no puede ser la constante NULL porque el tipo del primer argumento tiene ser conocido." Maneje esto usando "coalesce (FieldName, 0)" ... por ejemplo, seleccione 1 / nullif (coalesce (null, 0), 0)
John Joseph

1
@JohnJoseph No puedo decir si estás de acuerdo conmigo o discutiendo conmigo.
ErikE

2
@JohnJoseph Mira más de cerca el error que recibiste. Sí, SELECT 1 / NULLIF(NULL, 0)falla, pero es porque NULLIF()necesita saber el tipo de datos del primer argumento. Este ejemplo alterada funciona bien: SELECT 1 / NULLIF(CAST(NULL AS INT), 0). En la vida real, va a proporcionar una columna de tabla en NULLIF()lugar de una NULLconstante. Ya que las columnas de la tabla tienen tipos de datos conocidos, esto también funciona bien: SELECT 1 / NULLIF(SomeNullableColumn, 0) FROM SomeTable.
MarredCheese

180

En caso de que desee devolver cero, en caso de que ocurra un dispositivo cero, puede usar:

SELECT COALESCE(dividend / NULLIF(divisor,0), 0) FROM sometable

Por cada divisor que sea cero, obtendrá un cero en el conjunto de resultados.


99
Algunos puntos de referencia revelan que COALESCE es ligeramente más lento que ISNULL. Sin embargo, COALESCE está en los estándares, por lo que es más portátil.
Paul Chernoch

37
Si alguien más no entiende instantáneamente por qué esto funciona, NULLIF (d, 0) devolverá NULL si d es 0. En SQL, dividir por NULL devuelve NULL. The Coalesce reemplaza el NULL resultante por 0.
GuiSim

16
@SQLGeorge Aunque estoy de acuerdo con su argumento, tenga en cuenta que hay casos en los que a uno le importa más lo que es estadísticamente correcto que matemáticamente correcto. En algunos casos, cuando se utilizan funciones estadísticas, 0 o incluso 1 es un resultado aceptable cuando el divisor es cero.
Athafoud

10
¿Alguien puede explicarme por qué esto es malo? Si estoy tratando de calcular un porcentaje y el divisor es cero, sin duda quiero que el resultado sea cero por ciento.
Todd Sharp

10
Creo que @George y @ James / Wilson entienden mal la pregunta que se hace. Ciertamente, existen aplicaciones comerciales en las que es apropiado devolver un "0", incluso si no es técnicamente cierto desde un punto de vista matemático.
Sean Branchaw

66

Esta parece ser la mejor solución para mi situación cuando intento abordar la división entre cero, lo que sucede en mis datos.

Suponga que desea calcular las relaciones hombre-mujer para varios clubes escolares, pero descubre que la siguiente consulta falla y emite un error de división por cero cuando intenta calcular la relación para el Club del Señor de los Anillos, que no tiene mujeres :

SELECT club_id, males, females, males/females AS ratio
  FROM school_clubs;

Puede usar la función NULLIFpara evitar la división por cero. NULLIFcompara dos expresiones y devuelve nulo si son iguales o la primera expresión de lo contrario.

Reescribe la consulta como:

SELECT club_id, males, females, males/NULLIF(females, 0) AS ratio
  FROM school_clubs;

Cualquier número dividido por NULLda NULL, y no se genera ningún error.


66
Sí, de hecho, es MUCHO MEJOR que esa otra respuesta que ha recibido tantos votos positivos. En su solución, tiene al menos un NULL, que indica que no puede proporcionar un resultado correcto. Pero si convierte el resultado de NULL a cero, simplemente obtiene resultados incorrectos y engañosos.
SQL Police

8
Por cierto, si se desea calcular una razón hombre / mujer, entonces le sugiero a una mejor comparación al total, de esta manera: select males/(males+females), females/(males+females). Esto le dará la distribución porcentual de hombres y mujeres en un club, como 31% hombres, 69% mujeres.
SQL Police

44

También puede hacer esto al comienzo de la consulta:

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

Entonces, si tiene algo así 100/0, devolverá NULL. Solo he hecho esto para consultas simples, por lo que no sé cómo afectará a las más largas / complejas.


1
Funciona para mi. En mi caso, tengo que usar la operación de división en la cláusula WHERE. Estoy seguro de que no hay un divisor de cero, porque cuando comento WHERE out, no hay valores de cero en los resultados. Pero de alguna manera, el optimizador de consultas se divide por cero mientras se filtra. SET ARITHABORT OFF SET y ANSI_WARNINGS OFF funcionan - después de 2 días de pelear con dividir por cero en la cláusula WHERE. ¡Gracias!
huhu78

2
¡Esto "se siente" tan sucio pero me encanta! Lo necesitaba en una consulta que agregue y use la declaración CASE no era una opción porque luego tuve que agregar esa columna al GROUP BY que cambió totalmente los resultados. Hacer que la consulta inicial sea una subselección y luego hacer un GROUP BY en la consulta externa también cambia los resultados porque hay una división involucrada.
Andrew Steitz

1
Bien, todavía me gusta esta "solución", pero como muchos de ustedes probablemente sintieron, sentí que tenía que haber una forma "más limpia". ¿Qué sucede si olvidé volver a habilitar las advertencias? ¿O alguien disimuló mi código (eso nunca sucede, ¿verdad?) Y no pensó en las advertencias? De todos modos, vi otras respuestas sobre NULLIF (). Sabía acerca de NULLIF () pero no me di cuenta de que dividir por NULL devuelve NULL (pensé que sería un error). Entonces ... fui con lo siguiente: ISNULL ((SUM (foo) / NULLIF (SUM (bar), 0)), 0) AS Avg
Andrew Steitz

2
No conocía esta solución. No estoy seguro de que me guste, pero podría ser útil saberlo algún día. Muchas gracias.
Henrik Staun Poulsen

1
Esta es la solución más fácil, pero tenga en cuenta que perjudicará el rendimiento. De docs.microsoft.com/en-us/sql/t-sql/statements/… : "Establecer ARITHABORT en OFF puede tener un impacto negativo en la optimización de la consulta y generar problemas de rendimiento".
mono blaine

36

Al menos puede evitar que la consulta se rompa con un error y regresar NULLsi hay una división por cero:

SELECT a / NULLIF(b, 0) FROM t 

Sin embargo, NUNCA convertiría esto a Cero coalescecomo se muestra en esa otra respuesta que obtuvo muchos votos positivos. Esto es completamente incorrecto en un sentido matemático, e incluso es peligroso ya que su aplicación probablemente arrojará resultados incorrectos y engañosos.


32

EDITAR: Recibo muchos votos negativos sobre esto recientemente ... así que pensé en agregar una nota de que esta respuesta se escribió antes de que la pregunta se sometiera a su edición más reciente, donde el retorno nulo se destacó como una opción ... .que parece muy aceptable. Parte de mi respuesta se dirigió a preocupaciones como la de Edwardo, en los comentarios, que parecía estar abogando por devolver un 0. Este es el caso contra el que estaba criticando.

RESPUESTA: Creo que hay un problema subyacente aquí, que es que la división por 0 no es legal. Es una indicación de que algo está fundamentalmente mal. Si está dividiendo entre cero, está tratando de hacer algo que no tiene sentido matemáticamente, por lo que ninguna respuesta numérica que pueda obtener será válida. (El uso de nulo en este caso es razonable, ya que no es un valor que se utilizará en cálculos matemáticos posteriores).

Entonces, Edwardo pregunta en los comentarios "¿y si el usuario pone un 0?", Y defiende que debería estar bien obtener un 0 a cambio. Si el usuario pone cero en la cantidad, y desea que se devuelva 0 cuando lo hace, entonces debe poner un código en el nivel de reglas de negocio para capturar ese valor y devolver 0 ... no tiene algún caso especial donde la división por 0 = 0.

Esa es una diferencia sutil, pero es importante ... porque la próxima vez que alguien llame a su función y espere que haga lo correcto, y hace algo funky que no es matemáticamente correcto, pero solo maneja el caso particular, tiene un buenas posibilidades de morder a alguien más tarde. Realmente no estás dividiendo por 0 ... solo estás devolviendo una mala respuesta a una mala pregunta.

Imagina que estoy codificando algo y lo arruino. Debería estar leyendo en un valor de escala de medición de radiación, pero en un caso extraño que no anticipé, leí en 0. Luego dejo caer mi valor en su función ... ¡me devuelve un 0! ¡Hurra, no hay radiación! Excepto que realmente está allí y es solo que estaba pasando un mal valor ... pero no tengo idea. Quiero que la división arroje el error porque es la bandera de que algo está mal.


15
Estoy en desacuerdo. Las reglas de su negocio nunca deberían terminar haciendo matemática ilegal. Si termina haciendo algo como esto, lo más probable es que su modelo de datos esté equivocado. Cada vez que se encuentra con una división por 0 que debe ponderar si los datos debe había sido NULL en lugar de 0.
Remus Ruşanu

32
No puedo creer que alguien me haya votado negativamente si alguna vez "he hecho alguna programación real". porque digo que lo hagas bien, en lugar de ser flojo. suspiro
Beska

11
Lo siento, no quise ofenderte. Pero la pregunta es perfectamente válida en muchas aplicaciones comunes de LOB, y responderla con una "división por 0 no es legal" no agrega valor en mi humilde opinión.
Eduardo Molteni

2
@JackDouglas Derecha. Este es un caso en el que desea que las reglas de negocio manejen un caso especial de una manera especial ... pero no deberían ser las matemáticas subyacentes las que devuelven un espacio en blanco. Debería ser la regla de negocios. La respuesta aceptada devuelve un valor nulo, que es una buena manera de manejarlo. Lanzar una excepción también estaría bien. Descubrirlo y manejarlo antes de que vaya al SQL sería posiblemente ideal. Proporcionar algún tipo de función que otras cosas puedan llamar que devuelva un valor matemáticamente incorrecto no es el camino a seguir, ya que ese caso especial puede no aplicarse a esas otras personas que llaman.
Beska

44
@JackDouglas Sí, ese es un buen resumen, con el que estoy de acuerdo. Originalmente, la pregunta parecía estar redactada como "qué puedo hacer para ocultar este error". Desde entonces, ha evolucionado. Devolver un valor nulo, la respuesta a la que finalmente llega, parece una respuesta razonable. (Estaba abogando firmemente por no devolver un 0, o algún otro número.)
Beska

28
SELECT Dividend / ISNULL(NULLIF(Divisor,0), 1) AS Result from table

Al atrapar el cero con un nullif (), el nulo resultante con un isnull () puede evitar su error de división por cero.


3
Debido a su longitud, su respuesta se ha recomendado para su eliminación. Tenga en cuenta que siempre es mejor agregar una pequeña explicación de lo que esté sugiriendo, incluso si parece muy simple;)
Trinimon

10

Reemplazar "dividir por cero" con cero es controvertido, pero tampoco es la única opción. En algunos casos, reemplazar con 1 es (razonablemente) apropiado. A menudo me encuentro usando

 ISNULL(Numerator/NULLIF(Divisor,0),1)

cuando estoy viendo cambios en los puntajes / recuentos, y quiero establecer el valor predeterminado en 1 si no tengo datos. Por ejemplo

NewScore = OldScore *  ISNULL(NewSampleScore/NULLIF(OldSampleScore,0),1) 

La mayoría de las veces, he calculado esta relación en otro lugar (sobre todo porque puede arrojar algunos factores de ajuste muy grandes para denominadores bajos. En este caso, normalmente el control para OldSampleScore es mayor que un umbral; lo que luego excluye cero Pero a veces el 'pirateo' es apropiado.


1
@N Mason; Sí, a veces 1 es una opción. Pero, ¿cómo recuerdas la parte ISNULL cuando escribes la barra diagonal inversa?
Henrik Staun Poulsen

1
Lo siento Henrik, no estoy seguro de entender la pregunta.
N Mason

6

Escribí una función hace un tiempo para manejarla para mis procedimientos almacenados :

print 'Creating safeDivide Stored Proc ...'
go

if exists (select * from dbo.sysobjects where  name = 'safeDivide') drop function safeDivide;
go

create function dbo.safeDivide( @Numerator decimal(38,19), @divisor decimal(39,19))
   returns decimal(38,19)
begin
 -- **************************************************************************
 --  Procedure: safeDivide()
 --     Author: Ron Savage, Central, ex: 1282
 --       Date: 06/22/2004
 --
 --  Description:
 --  This function divides the first argument by the second argument after
 --  checking for NULL or 0 divisors to avoid "divide by zero" errors.
 -- Change History:
 --
 -- Date        Init. Description
 -- 05/14/2009  RS    Updated to handle really freaking big numbers, just in
 --                   case. :-)
 -- 05/14/2009  RS    Updated to handle negative divisors.
 -- **************************************************************************
   declare @p_product    decimal(38,19);

   select @p_product = null;

   if ( @divisor is not null and @divisor <> 0 and @Numerator is not null )
      select @p_product = @Numerator / @divisor;

   return(@p_product)
end
go

2
Hola Ron, buena solución, excepto que tiene un tipo de datos limitado (4 decimales) y nuestros @divisors también pueden ser negativos. ¿Y cómo haces cumplir su uso? TIA Henrik Staun Poulsen
Henrik Staun Poulsen

1
Lo eliminé bastante rápido para manejar un escenario de problema específico en ese momento. Aplicación de desarrollador único, por lo que la aplicación no es tan difícil a excepción de mi memoria. :-)
Ron Savage

55
A pesar de la declaración impresa, no es un proceso almacenado, es un UDF escalar. Esto lo matará en MS-SQL si es parte de una consulta.
Mark Sowul

44
Estuve de acuerdo con la afirmación de Mark Sowul de que la función escalar causará dolor. Esta es una sugerencia terrible en T-SQL, ¡no lo hagas! ¡Las funciones escalares son destructores de rendimiento! Las funciones con valores de tabla en línea son las únicas buenas funciones de usuario en SQL Server (posiblemente con la excepción de las funciones CLR que pueden funcionar bien).
Davos

4
  1. Agregue una restricción CHECK que obligue Divisora ser distinto de cero
  2. Agregue un validador al formulario para que el usuario no pueda ingresar valores cero en este campo.

1
Me empiezan a gustar las restricciones CHECK cada vez más.
Henrik Staun Poulsen

4

Para actualizar SQLs:

update Table1 set Col1 = Col2 / ISNULL(NULLIF(Col3,0),1)

3
hola Vijay, sí, eso funcionará, pero ... Tendría cuidado con la parte ISNULL, donde terminas dividiendo por NULL. Prefiero indicarle al usuario que el resultado es desconocido porque el divisor es cero.
Henrik Staun Poulsen

2
Me ha salvado en una subconsulta complicada, gracias.
QMaster

3

No existe una configuración global mágica "desactivar división por 0 excepciones". La operación tiene que lanzar, ya que el significado matemático de x / 0 es diferente del significado NULL, por lo que no puede devolver NULL. Supongo que se está ocupando de lo obvio y sus consultas tienen condiciones que deberían eliminar los registros con el divisor 0 y nunca evaluar la división. El 'problema' habitual es que la mayoría de los desarrolladores esperan que SQL se comporte como lenguajes de procedimiento y ofrezca un cortocircuito de operador lógico, pero NO . Le recomiendo que lea este artículo: http://www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html


44
Existe una "configuración global mágica": DESACTIVAR ARITHABORT.
David Manheim

3

Aquí hay una situación en la que puedes dividir por cero. La regla de negocio es que para calcular los turnos de inventario, usted toma el costo de los bienes vendidos por un período, lo anualiza. Después de tener el número anualizado, se divide por el inventario promedio del período.

Estoy considerando calcular la cantidad de vueltas de inventario que ocurren en un período de tres meses. He calculado que he vendido el costo de los bienes durante el período de tres meses de $ 1,000. La tasa anual de ventas es de $ 4,000 ($ 1,000 / 3) * 12. El inventario inicial es 0. El inventario final es 0. Mi inventario promedio ahora es 0. Tengo ventas de $ 4000 por año y no tengo inventario. Esto produce un número infinito de vueltas. Esto significa que todo mi inventario está siendo convertido y comprado por los clientes.

Esta es una regla comercial de cómo calcular los giros de inventario.


3
Sí, entonces tienes un número infinito de turnos. Entonces, en este caso, si tiene una división por cero, entonces debe mostrar algo como '#INF'.
SQL Police

1
"El inventario inicial es 0. El inventario final es 0. Mi inventario promedio ahora es 0." Tu cálculo es una estimación. En algún momento el inventario es positivo o no puede enviar / vender nada. Si no está satisfecho con + ∞ como resultado, use una mejor estimación del inventario promedio.
Tom Blodget

2
CREATE FUNCTION dbo.Divide(@Numerator Real, @Denominator Real)
RETURNS Real AS
/*
Purpose:      Handle Division by Zero errors
Description:  User Defined Scalar Function
Parameter(s): @Numerator and @Denominator

Test it:

SELECT 'Numerator = 0' Division, dbo.fn_CORP_Divide(0,16) Results
UNION ALL
SELECT 'Denominator = 0', dbo.fn_CORP_Divide(16,0)
UNION ALL
SELECT 'Numerator is NULL', dbo.fn_CORP_Divide(NULL,16)
UNION ALL
SELECT 'Denominator is NULL', dbo.fn_CORP_Divide(16,NULL)
UNION ALL
SELECT 'Numerator & Denominator is NULL', dbo.fn_CORP_Divide(NULL,NULL)
UNION ALL
SELECT 'Numerator & Denominator = 0', dbo.fn_CORP_Divide(0,0)
UNION ALL
SELECT '16 / 4', dbo.fn_CORP_Divide(16,4)
UNION ALL
SELECT '16 / 3', dbo.fn_CORP_Divide(16,3)

*/
BEGIN
    RETURN
        CASE WHEN @Denominator = 0 THEN
            NULL
        ELSE
            @Numerator / @Denominator
        END
END
GO

No me gusta su solución, porque el uso de un UDF obliga a la consulta a ejecutarse en un modo de subproceso único. Me gusta tu configuración de prueba. Me gustaría tener eso en todos nuestros UDF.
Henrik Staun Poulsen

Para mí, esta solución es perfecta y elegante
Payedimaunt

@Payedimaunt; Sí, los UDF conducen a un código muy elegante. Pero no funciona bien. Esto es "cursores sobre pastillas para dormir". :-) También es difícil recordar escribir dbo.Divide en lugar de un ordinario "/"
Henrik Staun Poulsen

1

Filtre los datos utilizando una cláusula where para que no obtenga valores 0.


1

A veces, 0 puede no ser apropiado, pero a veces 1 tampoco es apropiado. A veces, un salto de 0 a 100,000,000 descrito como 1 o 100 por ciento de cambio también puede ser engañoso. 100,000,000 por ciento podría ser apropiado en ese escenario. Depende de qué tipo de conclusiones pretendes sacar en función de los porcentajes o proporciones.

Por ejemplo, un artículo de venta muy pequeña que pasa de 2-4 vendido y un artículo de venta muy grande que cambia de 1,000,000 a 2,000,000 vendido podría significar cosas muy diferentes para un analista o para la administración, pero ambos aparecerían como 100% o 1 cambio.

Puede ser más fácil aislar valores NULL que recorrer un conjunto de filas 0% o 100% mezcladas con datos legítimos. A menudo, un 0 en el denominador puede indicar un error o un valor perdido, y es posible que no desee completar un valor arbitrario solo para que su conjunto de datos se vea ordenado.

CASE
     WHEN [Denominator] = 0
     THEN NULL --or any value or sub case
     ELSE [Numerator]/[Denominator]
END as DivisionProblem

1
Me parece que el problema es recordar hacer algo cada vez que quieras hacer una división. Si no recuerda agregar CASE o NULLIF, recibirá un caso de soporte en x semanas, un lunes por la mañana. Odio eso.
Henrik Staun Poulsen

1

Así es como lo arreglé:

IIF (ValorA! = 0, Total / ValorA, 0)

Se puede envolver en una actualización:

SET Pct = IIF (ValorA! = 0, Total / ValorA, 0)

O en un selecto:

SELECT IIF (ValueA! = 0, Total / ValueA, 0) AS Pct FROM Tablename;

Pensamientos?


Yo y otros encontramos que reemplazar "desconocido" por cero es una solución peligrosa. Prefiero mucho NULL. ¡Pero lo difícil es recordar agregar iif o nullif en todas las divisiones!
Henrik Staun Poulsen

0

Puede manejar el error de manera apropiada cuando se propaga nuevamente al programa de llamada (o ignorarlo si eso es lo que desea). En C #, cualquier error que ocurra en SQL arrojará una excepción que puedo detectar y luego manejar en mi código, al igual que cualquier otro error.

Estoy de acuerdo con Beska en que no quieres ocultar el error. Es posible que no esté tratando con un reactor nuclear, pero ocultar errores en general es una mala práctica de programación. Esta es una de las razones por las que los lenguajes de programación más modernos implementan el manejo estructurado de excepciones para desacoplar el valor de retorno real con un código de error / estado. Esto es especialmente cierto cuando estás haciendo matemáticas. El mayor problema es que no puede distinguir entre un 0 calculado correctamente o un 0 como resultado de un error. En cambio, cualquier valor devuelto es el valor calculado y, si algo sale mal, se genera una excepción. Esto, por supuesto, diferirá según cómo esté accediendo a la base de datos y qué idioma está utilizando, pero siempre debería poder recibir un mensaje de error con el que pueda lidiar.

try
{
    Database.ComputePercentage();
}
catch (SqlException e)
{
    // now you can handle the exception or at least log that the exception was thrown if you choose not to handle it
    // Exception Details: System.Data.SqlClient.SqlException: Divide by zero error encountered.
}

1
Creo que todos estamos de acuerdo en que ocultar el error con un 0 no es una solución. Lo que propongo es escribir nuestro código de modo que cada "/" vaya seguido de un "NULLIF". De esta manera, mi informe / reactor nuclear no se queda solo, sino que muestra un "NULL" en lugar de ese molesto error Msg 8134. De hecho, si necesito un proceso diferente cuando el divisor es 0, entonces Beska y yo estamos de acuerdo. Necesitamos codificar eso. Es difícil hacerlo cada vez que escribe un "/". "/ NULLIF" no es imposible de hacer cada vez.
Henrik Staun Poulsen

0

Use NULLIF(exp,0)pero de esta manera -NULLIF(ISNULL(exp,0),0)

NULLIF(exp,0)se rompe si exp es nullpero NULLIF(ISNULL(exp,0),0)no se romperá


1
No puedo obtener NULLIF (exp, 0), si 0 es un cero, no un o. Tratar; DECLARE @i INT SELECT 1 / NULLIF (@i, 0) para ver si puede hacer que se rompa.
Henrik Staun Poulsen
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.