如何避免SQL中的“被零除”错误?


363

我有此错误信息:

消息8134,级别16,状态1,第1行除以零错误。

编写SQL代码以使我再也看不到此错误消息的最佳方法是什么?

我可以执行以下任一操作:

  • 添加一个where子句,这样我的除数永远不会为零

要么

  • 我可以添加一个case语句,以便对零进行特殊处理。

使用NULLIF子句的最佳方法是吗?

有没有更好的方法,或者如何执行?


7
也许需要进行一些数据验证。
安东尼

Answers:


644

为了避免出现“被零除”错误,我们对此进行了如下编程:

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

但这是一种更好的方法:

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

现在唯一的问题是,如果我使用“ /”键,则要记住NullIf位。


13
如果除数为NULL,则执行“选择除数/ nullif(除数,0)...”的更好方法。
安德森(Anderson)

8
@安德森这根本不是真的。您确定您不是偶然使用IsNull而不是NullIf吗?自己尝试!SELECT Value,1/NullIf(Value,0)FROM(VALUES(0),(5.0),(NULL))x(Value);除非通过“ breaks”,否则您的意思是返回NULL?您可以使用IsNull或将其转换为所需的内容Coalesce
ErikE

1
@ErikE,这是真的...尝试运行...选择1 / nullif(null,0)...您将获得“ NULLIF的第一个参数的类型不能为NULL常量,因为第一个参数的类型具有众所周知。” 通过使用“ coalesce(FieldName,0)”处理此问题...例如,选择1 / nullif(coalesce(null,0),0)
约翰·约瑟夫

1
@JohnJoseph我不知道你是同意我还是争论我。
ErikE

1
@JohnJoseph仔细看看您遇到的错误。是的,SELECT 1 / NULLIF(NULL, 0)失败了,但这是因为NULLIF()需要知道第一个参数的数据类型。这个修改后的示例可以正常工作:SELECT 1 / NULLIF(CAST(NULL AS INT), 0)。在现实生活中,您将提供一个表列NULLIF()而不是一个NULL常量。由于表列具有已知的数据类型,因此也可以正常工作:SELECT 1 / NULLIF(SomeNullableColumn, 0) FROM SomeTable
MarredCheese

180

如果您想返回零,或者发生零除法,则可以使用:

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

对于除数为零的每个除数,您将在结果集中得到零。


9
一些基准测试表明,COALESCE比ISNULL稍慢。但是,COALESCE在标准中,因此更便于携带。
Paul Chernoch

37
如果其他人无法立即了解其工作原理,则如果d为0,则NULLIF(d,0)将返回NULL。在SQL中,除以NULL将返回NULL。聚结由0代替所得NULL
GuiSim

16
@SQLGeorge虽然我同意您的观点,但请注意,在某些情况下,人们更关心统计学上正确的是数学上正确的。在某些情况下,使用统计函数时,除数为零时,可接受的结果为0甚至1。
Athafoud '16

10
有人可以向我解释为什么这不好吗?如果我试图找出一个百分比并且除数为零,那么我当然希望结果为零。
托德·夏普

10
我认为@George和@ James / Wilson从根本上误解了所提出的问题。当然,在某些业务应用程序中,即使从数学角度来看,返回“ 0”在技术上也不是正确的,但返回“ 0”是适当的。
肖恩·布兰肖

66

尝试解决除以零的情况时,这似乎是我情况的最佳解决方案,这确实发生在我的数据中。

假设您要计算各种学校俱乐部的男女比例,但是您发现以下查询失败,并且在尝试为没有女性的指环王俱乐部计算比例时,出现零除错误。 :

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

您可以使用该函数NULLIF避免被零除。NULLIF比较两个表达式,如果相等则返回null,否则返回第一个表达式。

将查询重写为:

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

任何数字除以NULLGive NULL,都不会产生错误。


6
确实是这样,这是更好的方式比已经有这么多的upvotes,其他的答案。在您的解决方案中,您至少要有一个NULL,这表示您无法提供正确的结果。但是,如果将结果从NULL转换为零,那么您只会得到错误和令人误解的结果。
SQL警察

8
顺便说一句,如果您想计算男女比例,那么我建议将其与总数进行更好的比较,如下所示:select males/(males+females), females/(males+females)。这将为您提供俱乐部中男性和女性的百分比分布,例如31%的男性,69%的女性。
SQL警察

44

您也可以在查询开始时执行此操作:

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

因此,如果您有类似的东西100/0,它将返回NULL。我仅对简单查询执行了此操作,因此我不知道它将如何影响较长/复杂的查询。


1
为我工作。就我而言,我必须在WHERE子句中使用除法运算。我确定没有零除法器,因为当我在哪里注释时,结果中没有零值。但是查询优化器在过滤时确实会被零除。SET ARITHABORT OFF SET和ANSI_WARNINGS OFF可以正常工作-经过2天的战斗,在WHERE子句中被零除。谢谢!
huhu78 2012年

2
这个“感觉”很脏,但我喜欢它!在进行聚合并使用CASE语句的查询中需要它不是一种选择,因为那之后我不得不将该列添加到GROUP BY,这完全改变了结果。使初始查询成为子选择,然后对外部查询进行GROUP BY也会更改结果,因为其中涉及除法。
Andrew Steitz

1
好的,所以我仍然喜欢这个“解决方案”,但是就像你们中的许多人可能感觉到的那样,我觉得必须有一种“更清洁”的方式。如果我忘记重新启用警告怎么办?还是有人抄袭了我的代码(从来没有发生过,对吧?)而没有考虑警告?无论如何,看到了有关NULLIF()的其他答案。我知道NULLIF(),但没有意识到除以NULL会返回NULL(我认为这将是一个错误)。所以...我进行以下操作:ISNULL((SUM(foo)/ NULLIF(SUM(bar),0)),0)AS Avg
Andrew Steitz 2013年

2
我不知道这个解决方案。我不确定我是否喜欢它,但是有一天知道它可能会有用。非常感谢你。
Henrik Staun Poulsen 2013年

1
这是最简单的解决方案,但请注意,这会影响性能。来自docs.microsoft.com/zh-cn/sql/t-sql/statements/…:“将ARITHABORT设置为OFF可能会对查询优化产生负面影响,从而导致性能问题。”
mono blaine

36

您至少可以阻止查询因错误而中断,并返回NULL除以零的结果:

SELECT a / NULLIF(b, 0) FROM t 

但是,我永远不会coalesce像其他答案中显示的那样将其转换为零。从数学上讲,这是完全错误的,甚至很危险,因为您的应用程序可能会返回错误和误导性的结果。


32

编辑:最近我对此表示了很多反对...所以我想我只想补充一下,这个答案是在问题进行最新编辑之前写的,其中高亮显示返回null是一个选项。 。这似乎是可以接受的。我的一些回答是针对Edwardo之类的担忧,他们在评论中似乎主张返回0。这就是我所反对的情况。

答案:我认为这里有一个潜在的问题,那就是除以0是不合法的。这表明根本上有问题。如果您将其除以零,则您正在尝试做一些数学上没有意义的事情,因此您无法获得任何数字答案都是有效的。(在这种情况下使用null是合理的,因为在以后的数学计算中不会使用该值)。

因此Edwardo在评论中问“用户是否输入0?”,他主张可以得到0作为回报。如果用户将金额设置为零,并且您希望在这样做时返回0,那么您应该在业务规则级别输入代码以捕获该值并返回0 ...不存在除以0 =的特殊情况0。

这是一个细微的差异,但是很重要...因为下次有人调用您的函数并期望它执行正确的操作时,它执行的某些时髦操作在数学上是不正确的,但仅能处理特定的边沿情况以后再咬人的好机会。您并没有真正除以0 ...您只是在向一个错误的问题返回错误的答案。

想象一下我正在编码,然后搞砸了。我应该读取辐射测量的比例值,但是在一个意料不到的边缘情况下,我读取了0。然后将我的值放到您的函数中……给我返回0!万岁,没有辐射!除非它真的在那里而且只是我传递了一个错误的值...但是我不知道。我希望除法器抛出错误,因为这是某些问题的标记。


15
我不同意。您的业​​务规则永远永远都不会做非法数学。如果您最终做这样的事情,那么您的数据模型很可能是错误的。当你通过0遇到一个鸿沟,你应该思考,如果数据应该已经NULL而不是0
莱姆斯Rusanu

32
我不敢相信有人问我是否曾经做过任何真正的编程工作?因为我说的是做对事情,而不是偷懒。 感叹
Beska,

11
抱歉,我不是要冒犯您。但是,该问题在许多常见的LOB应用程序中完全正确,并且用“除以0是不合法的”来回答该问题不会增加IMHO的价值。
爱德华多·莫尔蒂尼

2
@JackDouglas对。在这种情况下,您希望业务规则以特殊方式处理特殊情况……但它不应是返回空白的基础数学。这应该是业务规则。接受的答案将返回null,这是处理它的一种好方法。抛出异常也可以。可以肯定的是,先找到它并在将其用于SQL之前进行处理。提供其他函数可以调用的,返回数学上不正确的值的某种函数并不是可行的方法,因为这种特殊情况可能不适用于那些其他调用者。
Beska 2012年

4
@JackDouglas是的,我同意,这是一个很好的总结。最初,该问题的措辞似乎是“我该怎么做才能隐藏此错误”。从那时起,它就发展了。返回空值,他最终得到的答案似乎是一种合理的反应。(我强烈主张不返回0或其他数字。)
Beska 2013年

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

通过使用nullif()捕获零,然后使用isnull()捕获结果为null,可以规避除以零的错误。


3
由于篇幅太长,建议删除您的答案。请注意,最好还是对您的建议加些小解释-即使看起来很简单;)
Trinimon

10

用零代替“被零除”是有争议的-但这也不是唯一的选择。在某些情况下,(合理地)替换为1是适当的。我经常发现自己在用

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

当我查看分数/计数的变化时,如果我没有数据,则想默认为1。例如

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

通常,我实际上已经在其他地方计算了该比率(尤其是因为它可以为低分母抛出一些非常大的调整因子。在这种情况下,我通常会控制OldSampleScore大于阈值;然后排除零)但是有时候“ hack”是适当的。


1
@N梅森; 是的,有时1是一个选择。但是,当您键入反斜杠时,如何记起ISNULL部分呢?
Henrik Staun Poulsen

1
抱歉,亨里克-我不确定我是否理解这个问题。
N Mason

6

我不久前写了一个函数来处理我的存储过程

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
嗨,罗恩,好的解决方案,除了它的数据类型有限(小数点后4位)而且我们的@divisors也可以是负数。以及您如何强制使用它?TIA Henrik Staun Poulsen
Henrik Staun Poulsen

1
我很快就将其冲破,以处理当时的特定问题。单一开发人员应用程序,因此除了我的记忆外,执行起来并不那么困难。:-)
罗恩·萨维奇

5
尽管有print语句,但它不是存储的proc,而是标量UDF。如果它是查询的一部分,则将在MS-SQL中杀死您。
Mark Sowul

4
我同意Mark Sowul的观点,即标量函数会引起疼痛。在T-SQL中,这是一个可怕的建议,不要这样做!标量函数是性能破坏器!内联表值函数是SQL Server中唯一好的用户函数(可能是CLR函数,它们可以很好地执行)。
达沃斯

4
  1. 添加一个强制Divisor为非零的CHECK约束
  2. 将验证器添加到窗体,以便用户不能在此字段中输入零值。

1
我开始越来越喜欢CHECK约束。
Henrik Staun Poulsen,2010年

4

对于更新SQL:

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

3
嗨,Vijay,是的,这将起作用,但是...我会留意ISNULL部分,在该部分您最终将被NULL除。我宁愿通知用户结果未知,因为除数为零。
Henrik Staun Poulsen

2
谢谢,它为我省去了复杂的subQuery。
QMaster

3

没有魔术全局设置“关闭除以0的异常”。由于x / 0的数学含义不同于NULL的含义,因此必须抛出该操作,因此它不能返回NULL。我假设您正在处理显而易见的问题,并且您的查询具有应消除除数为0的记录且从不评估除法的条件。通常,“陷阱”是大多数开发人员所期望的,SQL的行为类似于过程语言并提供逻辑运算符短路,但事实并非如此。我建议您阅读这篇文章:http : //www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html


4
有一个这样的“魔术全局设置”; SET ARITHABORT OFF。
大卫·曼海姆

3

在这种情况下,您可以除以零。业务规则是,要计算库存周转率,您需要计算一段时间的销售成本并将其年化。获得年度数字后,用该时期的平均库存除以。

我正在计算三个月内发生的库存周转次数。我计算出在三个月的时间里售出的商品成本为$ 1,000。年销售率为$ 4,000($ 1,000 / 3)* 12。期初库存为0。期末库存为0。我现在的平均库存为0。我每年的销售额为$ 4000,并且没有库存。这产生了无限的匝数。这意味着我所有的库存都在被客户转换和购买。

这是如何计算库存周转率的业务规则。


3
是的,那么您可以无限次旋转。因此,在这种情况下,如果除以零,则应显示“ #INF”之类的内容。
SQL警察

1
“开始的库存为0。结束的库存为0。我的平均库存现在为0。” 您的计算是估算值。有时库存是正数,否则您将无法发货。如果您不满意+∞,请使用更好的平均库存估算。
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

我不喜欢您的解决方案,因为使用UDF会强制查询以单线程模式运行。我喜欢您的测试设置。我想在我们所有的UDF中都使用它。
Henrik Staun Poulsen

对我来说,这个解决方案是完美而优雅的
Payedimaunt

@Payedimaunt; 是的,UDF可以生成非常精美的代码。但是它表现不佳。这是“安眠药上的光标”。:-)也很难记住写dbo.Divide而不是普通的“ /”
Henrik Staun Poulsen


1

有时0可能不合适,但有时1也不合适。有时,从0到100,000,000的变化(描述为1或100%的变化)也可能会产生误导。在这种情况下,100,000,000%可能是合适的。这取决于您打算根据百分比或比率得出什么样的结论。

例如,一个很小的销售商品从2-4个出售,而一个很大的销售商品从1,000,000变为2,000,000,对于分析师或管理层来说可能意味着完全不同的东西,但两者都会以100%或1更改。

隔离NULL值可能要比遍历一堆0%或100%的合法数据行要容易得多。通常,分母中的0可能表示错误或缺失值,并且您可能不想仅填写任意值只是为了使数据集看起来整洁。

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

1
我发现问题在于记住要进行除法操作。如果您不记得添加CASE或NULLIF,则在一个星期一的早晨x周内获得支持案例。我讨厌那个。
Henrik Staun Poulsen

1

这是我解决的方法:

IIF(ValueA!= 0,总计/ ValueA,0)

它可以包装在一个更新中:

SET Pct = IIF(ValueA!= 0,总计/ ValueA,0)

或选择:

从表名中选择SELECT IIF(ValueA!= 0,Total / ValueA,0)AS Pct;

有什么想法吗?


我和其他人发现用零代替“未知”是一个危险的解决方法。我更喜欢NULL。但是困难的一点是要记住在所有分区上添加iif或nullif!
亨里克·斯塔恩·波尔森

0

当错误传播回调用程序时,您可以适当地处理该错误(如果需要,可以忽略该错误)。在C#中,SQL中发生的任何错误都会引发一个异常,我可以捕获该异常然后在我的代码中对其进行处理,就像其他任何错误一样。

我同意Beska,因为您不想隐藏该错误。您可能没有在处理核反应堆,但通常隐藏错误是不好的编程习惯。这是大多数现代编程语言实现结构化异常处理以将实际返回值与错误/状态代码分离的原因之一。在进行数学运算时尤其如此。最大的问题是您无法区分返回的是正确计算的0还是由于错误而导致的0。取而代之的是,返回的任何值都是计算所得的值,如果出现任何问题,将引发异常。当然,这将取决于您访问数据库的方式和所使用的语言而有所不同,但是您应该始终能够获得可以处理的错误消息。

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
我认为我们都同意,用0隐藏错误不是解决方案。我建议编写代码,使每个“ /”后跟一个“ NULLIF”。这样,我的报告/核反应堆就不会一个人呆着,而是显示“ NULL”而不是令人讨厌的错误Msg8134。当然,如果除数为0时我需要一个不同的过程,那么Beska和我同意。我们需要对此进行编码。每次写“ /”都很难做到。并非并非每次都执行“ / NULLIF”。
Henrik Staun Poulsen

0

使用NULLIF(exp,0),但以这种方式-NULLIF(ISNULL(exp,0),0)

NULLIF(exp,0)如果exp是nullNULLIF(ISNULL(exp,0),0)不会破坏则中断


1
如果0为零而不是o,则无法获取NULLIF(exp,0)。尝试; 宣告@i INT SELECT 1 / NULLIF(@i,0)以查看是否可以中断它。
Henrik Staun Poulsen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.