如何避免SQL中的"除以零"错误?

Hen*_*sen 339 sql sql-server sql-server-2005 sql-server-2008

我有这个错误消息:

Msg 8134,Level 16,State 1,Line 1遇到零误差.

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

我可以做以下任何一种情况:

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

要么

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

是使用NULLIF条款的最佳方式吗?

有没有更好的方法,或者如何实施?

Hen*_*sen 602

为了避免"除零"错误,我们将其编程为:

Select Case when divisor=0 then null
Else dividend / divisor
End ,,,
Run Code Online (Sandbox Code Playgroud)

但这是一个更好的方法:

Select dividend / NULLIF(divisor, 0) ...
Run Code Online (Sandbox Code Playgroud)

现在唯一的问题是记住NullIf位,如果我使用"/"键.

  • 一个更好的方法这样做"选择dividend/nullif(除数,0)......"如果除数为NULL则中断. (13认同)
  • @Anderson这根本不是真的.你确定你不小心使用`IsNull`而不是'NullIf`?亲自尝试一下!`SELECT Value,1/NullIf(Value,0)FROM(VALUES(0),(5.0),(NULL))x(Value);`除非"break"表示返回NULL?您可以使用`IsNull`或`Coalesce`将其转换为您想要的任何内容. (7认同)
  • @JohnJoseph 仔细看看你得到的错误。是的,`SELECT 1 / NULLIF(NULL, 0)` 失败了,但这是因为 `NULLIF()` 需要知道第一个参数的数据类型。这个修改过的例子工作正常:`SELECT 1 / NULLIF(CAST(NULL AS INT), 0)`。在现实生活中,您将向 `NULLIF()` 提供一个表列,而不是一个 `NULL` 常量。由于表列具有已知的数据类型,这也可以正常工作:`SELECT 1 / NULLIF(SomeNullableColumn, 0) FROM SomeTable`。 (5认同)
  • @ErikE,这是真的...尝试运行...选择1/nullif(null,0)...你得到“NULLIF的第一个参数的类型不能是NULL常量,因为第一个参数的类型有被人知道。” 通过使用“coalesce(FieldName,0)”来处理这个......例如选择1/nullif(coalesce(null,0),0) (2认同)
  • @JohnJoseph 我不知道你是同意我的观点还是与我争论。 (2认同)
  • 如果您不知道数据类型,除非您按@JohnJoseph 提供的示例中的字面意思键入“null”,否则怎么可能得到一个空除数?如果它来自数据表或变量,它将具有定义的类型。除非我遗漏了什么,否则听起来打破它的唯一方法就是故意去做。 (2认同)

Tob*_*han 169

如果你想返回零,万一发生零分割,你可以使用:

SELECT COALESCE(dividend / NULLIF(divisor,0), 0) FROM sometable
Run Code Online (Sandbox Code Playgroud)

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

  • 如果其他人没有立即得到这个工作的原因,如果d为0,则NULLIF(d,0)将返回NULL.在SQL中,除以NULL返回NULL.Coalesce将结果NULL替换为0. (35认同)
  • @SQLGeorge虽然我同意你的观点,但请注意,有些情况下,人们更关心统计上的正确性而不是数学正确性.在使用统计函数的某些情况下,当除数为零时,0或甚至1是可接受的结果. (16认同)
  • 有人可以向我解释为什么这很糟糕吗?如果我试图找出百分比并且除数为零,我肯定希望结果为零百分比. (10认同)
  • 我认为@George和@James/Wilson从根本上误解了被问到的问题.当然,业务应用程序中返回"0"是合适的,即使从数学角度来看技术上并不正确. (10认同)
  • 一些基准测试显示,COALESCE略慢于ISNULL.但是,COALESCE符合标准,因此更具便携性. (9认同)
  • **请**!令我震惊的是,这得到了71票!除以零是不明确的,不应该**返回ZERO!我很少投票,但这真是废话!至少请删除coalesce,然后除法返回NULL而不是Zero,这样更好. (7认同)
  • 做这样的黑客行为会耗费人力,失去工作,并可能危及生命.绝不应以任何形式接受此类答案.如果你想要返回0(或1),那么你做错了什么或者提出错误的问题.(Anything/0)如果它实际上是0那么你做错了什么就会返回错误.这意味着你需要走上一条线并找出你为什么得到0或者可以接受返回null.在这种情况下,0永远不会正确. (4认同)
  • @George驱动我到这个页面的特定例子是一个熟练程序跟踪器,计算从所选择的所有值中选择了多少符合某个标准的值.在用户输入任何数据之前,计算的初始状态为0/0,因为没有选择任何值.虽然这显然在技术上是不确定的,但告诉经理并不是特别有用.返回null或N/A可能是您的下一个选择,但不仅仅是在仪表板上直观地理解它是困惑的,我使用的应用程序不是很灵活.你为什么不同意? (4认同)
  • 还要注意,当被除数或除数为NULL时它返回0,而按标准它将返回NULL. (2认同)

小智 64

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

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

SELECT club_id, males, females, males/females AS ratio
  FROM school_clubs;
Run Code Online (Sandbox Code Playgroud)

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

将查询重写为:

SELECT club_id, males, females, males/NULLIF(females, 0) AS ratio
  FROM school_clubs;
Run Code Online (Sandbox Code Playgroud)

任何数字除以NULL给定NULL,不会产生错误.

  • 顺便说一句,如果你想计算一个男/女的比例,那么我建议将它与总数进行比较,比如:`选择男性/(男性+女性),女性/(男性+女性).这将为您提供俱乐部中男性和女性的百分比分布,例如31%的男性,69%的女性. (8认同)
  • 确实如此,这比其他有很多赞成的答案更好**.在您的解决方案中,您至少有一个NULL,表示您无法提供正确的结果.但是如果你将结果从NULL转换为零,那么你就会出错并误导结果. (5认同)

小智 43

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

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF
Run Code Online (Sandbox Code Playgroud)

所以如果你有类似的东西100/0会返回NULL.我只是为简单的查询做了这个,所以我不知道它会如何影响更长/更复杂的查询.

  • 这个“感觉”很脏,但我喜欢它!在进行聚合并使用CASE语句的查询中需要它不是一种选择,因为那之后我不得不将该列添加到GROUP BY,这完全改变了结果。将初始查询设为子选择,然后对外部查询执行GROUP BY也会更改结果,因为其中涉及除法。 (2认同)
  • 我不知道这个解决方案。我不确定我是否喜欢它,但是有一天知道它可能会有用。非常感谢你。 (2认同)

SQL*_*ice 33

您至少可以阻止查询中断错误,NULL如果存在除以零则返回:

SELECT a / NULLIF(b, 0) FROM t 
Run Code Online (Sandbox Code Playgroud)

但是,我永远不会将其转换为零,coalesce就像在其他答案中显示的那样得到了很多赞成.这在数学意义上是完全错误的,甚至是危险的,因为您的应用程序可能会返回错误和误导性的结果.


Bes*_*ska 32

编辑:我最近得到了很多关于这个... ...所以我想我只是添加一个注释,这个答案是在问题进行之前编写的,这是最近的编辑,其中返回null被突出显示为一个选项.. .这似乎非常可以接受.我的一些答案是针对爱德华的关注,在评论中,似乎主张回归0.这就是我所反对的情况.

答案:我认为这里存在一个潜在的问题,即除以0是不合法的.这表明某些事情是错误的.如果你除以零,你就会尝试做一些数学上没有意义的事情,所以你得到的数字答案都不会有效.(在这种情况下使用null是合理的,因为它不是将在以后的数学计算中使用的值).

所以Edwardo在评论中询问"如果用户输入0怎么办?",他主张应该可以获得0作为回报.如果用户在金额中加零,并且你希望在他们这样做时返回0,那么你应该在业务规则级别放入代码来捕获该值并返回0 ...没有一些特殊情况,除以0 = 0.

这是一个微妙的区别,但它很重要...因为下次有人调用你的函数并期望它做正确的事情,并且它做了一些在数学上不正确的时髦,但只是处理特定的边缘情况它有一个以后咬人的好机会.你真的没有被0分开......你只是回答了一个糟糕的问题.

想象一下,我正在编写一些东西,我搞砸了.我应该在辐射测量比例值中读取,但在一个奇怪的边缘情况下我没有预料到,我在0中读到.然后我将我的价值放入你的功能......你给我一个0!华友世纪,没有辐射!除了它真的存在,只是我传递了一个不好的价值......但我不知道.我希望分区抛出错误,因为它是错误的标志.

  • 我不敢相信有人会问我是否"做过任何真正的编程?" 因为我说要做得对,而不是懒惰.*叹* (32认同)
  • 我不同意.您的业​​务规则永远不应该最终进行非法数学运算.如果您最终做这样的事情,那么您的数据模型很可能是错误的.每当你遇到除以0时,你应该思考数据是否应该是NULL而不是0. (15认同)
  • 对不起,我不是故意冒犯你.但是这个问题在很多常见的LOB应用程序中都是完全有效的,并且用"0除法不合法"来回答这个问题并没有增加价值恕我直言. (11认同)
  • @JackDouglas是的,这是一个很好的总结,我同意.最初这个问题似乎被称为"我能做些什么才能隐藏这个错误." 从那以后,它已经发展.返回null,他最终得到的答案,似乎是一个合理的反应.(我强烈主张不返回0或其他数字.) (4认同)
  • @JackDouglas对。在这种情况下,您希望业务规则以特殊的方式处理特殊的情况……但它不应该是返回空白的基础数学。这应该是业务规则。接受的答案将返回null,这是处理它的一种好方法。抛出异常也可以。可以肯定的是,先找到它并在将其用于SQL之前进行处理。提供其他函数可能会返回的数学上不正确的值的某种函数并不是走的路,因为这种特殊情况可能不适用于那些其他调用者。 (2认同)

小智 26

SELECT Dividend / ISNULL(NULLIF(Divisor,0), 1) AS Result from table
Run Code Online (Sandbox Code Playgroud)

通过使用nullif()捕获零,然后使用isnull()得到的null可以绕过除以零的错误.

  • 由于其长度,建议您删除答案.请注意,总是更好地添加一些你建议的小解释 - 即使看起来非常简单;) (2认同)

小智 10

用零除"零除"是有争议的 - 但它也不是唯一的选择.在某些情况下,替换为1是(合理地)合适的.我经常发现自己在使用

 ISNULL(Numerator/NULLIF(Divisor,0),1)
Run Code Online (Sandbox Code Playgroud)

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

NewScore = OldScore *  ISNULL(NewSampleScore/NULLIF(OldSampleScore,0),1) 
Run Code Online (Sandbox Code Playgroud)

我经常在其他地方计算这个比例(尤其是因为它可以为低分母投入一些非常大的调整因子.在这种情况下,我通常会控制OldSampleScore大于阈值;然后排除零但有时候"黑客"是合适的.


Ron*_*age 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
Run Code Online (Sandbox Code Playgroud)

  • 尽管有print语句,但它不是存储过程,它是一个标量UDF.如果它是查询的一部分,这将在MS-SQL中杀死你. (5认同)
  • 我同意Mark Sowul断言标量函数会引起疼痛.这是T-SQL中一个可怕的建议,不要这样做!标量函数是性能驱逐者!内联表值函数是SQL Server中唯一的好用户函数(可能除了可以很好地执行的CLR函数之外). (3认同)
  • 嗨,罗恩,好的解决方案,除了它的数据类型有限(小数点后4位)而且我们的@divisors也可以是负数。以及您如何强制使用它?TIA Henrik Staun Poulsen (2认同)