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:
Ou
La meilleure façon d’utiliser une clause NULLIF
?
Existe-t-il un meilleur moyen ou comment cela peut-il être appliqué?
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 "/".
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.
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.
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.
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.
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.
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.
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é.
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
Divisor
à être différent de zéroPour les SQL de mise à jour:
update Table1 set Col1 = Col2 / ISNULL(NULLIF(Col3,0),1)
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.
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
Filtrez les données en utilisant une clause where afin de ne pas obtenir 0.
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
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
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.
}
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
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?