web-dev-qa-db-fra.com

Comment éviter l'erreur "diviser par zéro" en SQL?

J'ai ce message d'erreur:

Msg 8134, Niveau 16, État 1, Ligne 1 Divide par zéro erreur rencontrée.

Quel est le meilleur moyen d'écrire du code SQL pour que je ne voie plus jamais ce message d'erreur?

Je pourrais faire l'une des choses suivantes:

  • Ajouter une clause where pour que mon diviseur ne soit jamais nul

Ou

  • Je pourrais ajouter une déclaration de cas, de sorte qu'il existe un traitement spécial pour zéro. 

La meilleure façon d’utiliser une clause NULLIF

Existe-t-il un meilleur moyen ou comment cela peut-il être appliqué?

288

Afin d'éviter une erreur "Division par zéro", nous l'avons programmée comme suit:

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

Mais voici une façon beaucoup plus agréable de le faire:

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

Maintenant, le seul problème est de rappeler le bit NullIf, si j'utilise la touche "/".

533

Si vous voulez renvoyer zéro, dans le cas où une division par zéro se produirait, vous pouvez utiliser

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

Pour chaque diviseur égal à zéro, vous obtiendrez un zéro dans le jeu de résultats.

150
Tobias Domhan

Cela semblait être la meilleure solution pour ma situation lorsque j'essayais de résoudre le problème de la division par zéro, ce qui se produit dans mes données.

Supposons que vous souhaitiez calculer les ratios hommes/femmes pour différents clubs scolaires, mais que vous découvriez que la requête suivante échoue et génère une erreur de division par zéro lorsqu'elle tente de calculer un ratio pour le club du Seigneur des Anneaux, qui n'a pas de femme : 

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

Vous pouvez utiliser la fonction NULLIF pour éviter la division par zéro. NULLIF compare deux expressions et renvoie null si elles sont égales ou la première expression sinon. 

Réécrivez la requête en tant que: 

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

Tout nombre divisé par NULL donne NULL et aucune erreur n'est générée.

58
frank

Vous pouvez également le faire au début de la requête:

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

Donc, si vous avez quelque chose comme 100/0, il retournera NULL. Je ne l'ai fait que pour des requêtes simples, donc je ne sais pas comment cela affectera les requêtes plus longues/complexes.

39
Taz

EDIT: Je reçois beaucoup de votes négatifs à ce sujet récemment ... alors j’ai pensé ajouter une note indiquant que cette réponse avait été écrite avant que la question ne soit soumise à la dernière édition, où null a été mis en surbrillance. option ... qui semble très acceptable. Une partie de ma réponse a été adressée à des préoccupations telles que celle d'Edwardo, dans les commentaires, qui semblaient préconiser le retour d'un 0. C'est le cas contre lequel je plaidais.

RÉPONSE: Je pense qu'il y a un problème sous-jacent ici, à savoir que la division par 0 n'est pas légale. C'est une indication que quelque chose est fondamentalement faux. Si vous divisez par zéro, vous essayez de faire quelque chose qui n'a pas de sens mathématiquement, donc aucune réponse numérique que vous pouvez obtenir ne sera valide. (L'utilisation de null dans ce cas est raisonnable, car ce n'est pas une valeur qui sera utilisée dans des calculs mathématiques ultérieurs).

Donc, Edwardo demande dans les commentaires "que se passe-t-il si l'utilisateur met un 0?", Et il préconise que l'on puisse obtenir un 0 en retour. Si l'utilisateur met zéro dans le montant et que vous souhaitez que 0 soit renvoyé, vous devez alors entrer du code au niveau des règles commerciales pour capturer cette valeur et renvoyer 0 ... sans avoir de cas particulier dans lequel la division par 0 = 0. 

C'est une différence subtile, mais c'est important ... parce que la prochaine fois que quelqu'un appellera votre fonction et s'attendra à ce qu'elle fasse la bonne chose, et qu'il fait quelque chose de génial qui n'est pas mathématiquement correct, mais ne gère que le cas particulier d'Edge, il a bonne chance de mordre quelqu'un plus tard. Vous ne divisez pas vraiment par 0 ... vous ne faites que donner une mauvaise réponse à une mauvaise question.

Imaginez que je code quelque chose et que je le rate. Je devrais lire une valeur d'échelle de mesure de rayonnement, mais dans un cas étrange d'Edge que je n'avais pas anticipé, j'ai lu dans 0. Je laisse alors tomber ma valeur dans votre fonction ... vous me retournez un 0! Hourra, pas de radiation! Sauf que c'est vraiment là et c'est juste que je passais dans une mauvaise valeur ... mais je n'en ai aucune idée. Je veux que la division jette l'erreur parce que c'est le signe que quelque chose ne va pas.

31
Beska

Vous pouvez au moins empêcher la requête de rompre avec une erreur et renvoyer NULL s'il existe une division par zéro:

SELECT a / NULLIF(b, 0) FROM t 

Cependant, je voudraisJAMAISconvertir ceci en zéro avec coalesce comme cela est montré dans cette autre réponse qui a reçu beaucoup de votes positifs. C'est complètement faux au sens mathématique, et c'est même dangereux car votre application donnera probablement des résultats faux et trompeurs. 

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

En attrapant le zéro avec un nullif (), puis le null résultant avec un isnull (), vous pouvez contourner votre erreur de division par zéro. 

21
Gurufaction

Remplacer "diviser par zéro" par zéro est controversé - mais ce n’est pas la seule option. Dans certains cas, remplacer par 1 est (raisonnablement) approprié. Je me trouve souvent en train d'utiliser

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

quand je regarde les changements dans les scores/comptes, et que je veux passer à 1 par défaut si je n'ai pas de données. Par exemple

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

Plus souvent qu'autrement, j'ai effectivement calculé ce ratio ailleurs (notamment parce qu'il peut générer des facteurs d'ajustement très importants pour les faibles dénominateurs. Dans ce cas, je contrôlerais normalement que OldSampleScore soit supérieur à un seuil; Mais parfois, le «bidouillage» est approprié.

7
N Mason

J'ai écrit une fonction il y a quelque temps pour la gérer pour mes procédures stockées :

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
5
Ron Savage
  1. Ajouter une contrainte CHECK qui force Divisor à être différent de zéro
  2. Ajoutez un validateur au formulaire afin que l'utilisateur ne puisse pas entrer de valeur zéro dans ce champ.
3
finnw

Pour les SQL de mise à jour:

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

Voici une situation où vous pouvez diviser par zéro. La règle de base est que, pour calculer la rotation des stocks, vous prenez le coût des marchandises vendues pendant une période, vous l'annualisez. Après avoir obtenu le nombre annualisé, vous divisez par l'inventaire moyen de la période. 

Je cherche à calculer le nombre de tours d'inventaire qui se produisent sur une période de trois mois. J'ai calculé que le coût des produits avait été vendu au cours de la période de trois mois de 1 000 $. Le taux de vente annuel est de 4 000 $ (1 000 $/3) * 12. Le stock initial est 0. Le stock final est 0. Mon stock moyen est maintenant de 0. J'ai des ventes de 4 000 $ par an et aucun stock. Cela donne un nombre infini de tours. Cela signifie que tout mon inventaire est converti et acheté par les clients. 

Il s’agit d’une règle de gestion permettant de calculer les tournants de l’inventaire. 

2
Jimmy

Il n’existe pas de paramètre global magique permettant de désactiver la division par 0 exception. L'opération doit être lancée, car la signification mathématique de x/0 est différente de la signification de NULL, elle ne peut donc pas renvoyer NULL . Je suppose que vous vous occupez de l'évidence et que vos requêtes ont des conditions qui devraient éliminer les enregistrements avec le diviseur 0 et ne jamais évaluer la division. Le 'gotcha' habituel est que la plupart des développeurs s'attendent à ce que SQL se comporte comme un langage procédural et offre un court-circuit aux opérateurs logiques, mais il ne fait pas NI. Je vous recommande de lire cet article: http://www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html

2
Remus Rusanu

Filtrez les données en utilisant une clause where afin de ne pas obtenir 0.

1
nunespascal
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
1
Gregory Hart

Parfois, 0 peut ne pas être approprié, mais parfois 1 n'est pas non plus approprié. Parfois, un saut de 0 à 100 000 000 décrit comme un changement de 1 ou 100% peut également être trompeur. 100 000 000 pour cent pourraient être appropriés dans ce scénario. Cela dépend du type de conclusions que vous avez l'intention de tirer en fonction des pourcentages ou des ratios. 

Par exemple, un très petit article vendu entre 2 et 4 vendus et un article très vendu passant de 1 000 000 à 2 000 000 vendus pourrait avoir des sens très différents pour un analyste ou pour la direction, mais aboutirait à 100% ou 1 changement. 

Il peut être plus facile d’isoler les valeurs NULL que de parcourir une série de lignes à 0% ou à 100% mélangées à des données légitimes. Souvent, un 0 dans le dénominateur peut indiquer une erreur ou une valeur manquante, et vous ne voudrez peut-être pas simplement renseigner une valeur arbitraire simplement pour que votre ensemble de données soit soigné.

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

Vous pouvez gérer l'erreur de manière appropriée lorsqu'elle repasse dans le programme appelant (ou l'ignorer si c'est ce que vous voulez). En C #, toutes les erreurs qui se produisent dans SQL génèrent une exception que je peux intercepter puis gérer dans mon code, comme toute autre erreur.

Je suis d'accord avec Beska en ce que vous ne voulez pas cacher l'erreur. Vous n’avez peut-être pas affaire à un réacteur nucléaire, mais le fait de cacher des erreurs est en général une mauvaise pratique de programmation. C'est l'une des raisons pour lesquelles la plupart des langages de programmation modernes implémentent la gestion structurée des exceptions pour découpler la valeur de retour réelle avec un code d'erreur/d'état. Cela est particulièrement vrai lorsque vous faites des mathématiques. Le plus gros problème est que vous ne pouvez pas faire la distinction entre un 0 correctement calculé et son 0 ou le résultat d'une erreur. Au lieu de cela, toute valeur renvoyée est la valeur calculée et, en cas de problème, une exception est levée. Bien entendu, cela diffère en fonction de la manière dont vous accédez à la base de données et de la langue que vous utilisez, mais vous devriez toujours pouvoir obtenir un message d'erreur que vous pouvez gérer.

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.
}
0
Despertar

Utilisez NULLIF(exp,0) mais de cette manière - NULLIF(ISNULL(exp,0),0)

NULLIF(exp,0) rompt si exp est null mais NULLIF(ISNULL(exp,0),0) ne rompt pas

0
Johnny Kancharla

Voici comment je l'ai corrigé:

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

Il peut être encapsulé dans une mise à jour:

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

Ou dans une sélection:

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

Pensées?

0
CorvetteGuru