如何避免SQL中的“除以零”错误?
我有这个错误消息:
Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.
编写SQL代码的最佳方法是什么,以便我再也不会看到这个错误消息了?
我可以做以下任何一项:
- 添加一个where子句,使我的除数永远不为零
要么
- 我可以添加一个case语句,以便有一个特殊的零处理。
是使用NullIf子句的最佳方法?
有没有更好的办法,或者如何执行?
为了避免“零除”错误,我们已经这样编程了:
Select Case when divisor=0 then null Else dividend / divisor End ,,,
但是这样做是一个更好的方法:
Select dividend / nullif(divisor, 0) ...
现在唯一的问题是记住NullIf位,如果我使用“/”键。
如果你想返回零,万一发生零分解,你可以使用:
SELECT COALESCE(dividend / NULLIF(divisor,0), 0) FROM sometable
对于每个零除数,您将在结果集中得到一个零。
这似乎是解决我的情况时最好的解决办法,当试图解决零分,这发生在我的数据。
假设你想计算各个学校俱乐部的男女比率,但是你发现以下的查询失败了,并且当它试图计算没有女性的指环王俱乐部的比率时,它会发出一个被零除的错误:
SELECT club_id, males, females, males/females AS ratio FROM school_clubs;
您可以使用函数NULLIF
来避免被零除。 NULLIF
比较两个expression式,如果相等则返回null,否则返回第一个expression式。
将查询重写为:
SELECT club_id, males, females, males/NULLIF(females, 0) AS ratio FROM school_clubs;
任何除以NULL
都会给出NULL
,并且不会生成错误。
您也可以在查询的开头执行此操作:
SET ARITHABORT OFF SET ANSI_WARNINGS OFF
所以如果你有类似100/0
东西,它会返回NULL。 我只做了这个简单的查询,所以我不知道它会影响更长的/复杂的。
编辑:我得到了很多这最近downvotes …所以我想我只是添加一个注意到,这个答案写在问题之前经历了最近的编辑,其中返回null被突出显示为一个选项..这似乎是非常可以接受的。 我的一些答复是针对像爱德华多这样的关注,在评论中,他似乎主张返回0.这就是我所抗议的情况。
回答:我认为这里有一个潜在的问题,那就是除以0是不合法的。 这是一个迹象表明某件事情是非常糟糕的。 如果你除以零,你试图做一些没有意义的math,所以没有数字答案,你可以得到的将是有效的。 (在这种情况下使用null是合理的,因为它不是将在以后的math计算中使用的值)。
因此,爱德华在“如果用户放入一个0?”的评论中提出这样的问题,他主张可以得到0作为回报。 如果用户把数量设置为零,并且你想在返回时返回0,那么你应该在业务规则级别放置代码来捕获该值并返回0 …没有一些特殊情况,其中除0 = 0。
这是一个微妙的差异,但重要的是…因为下一次有人调用你的函数,并期望它做正确的事情,它做一些不是math上正确的时髦,但只是处理特定的边缘情况下它有一个后来咬人的好机会。 你不是真的被0分开……你只是回答了一个糟糕的问题。
想象一下,我正在编码的东西,我搞砸了。 我应该阅读辐射测量的缩放值,但在一个奇怪的边缘情况下,我没有预料到,我读了0.然后我的价值降到你的function…你回到我0! 万岁,没有辐射! 除了它真的在那里,只是我传递了一个坏的价值…但我不知道。 我想分裂抛出的错误,因为这是一个错误的标志。
您至less可以停止查询,并返回NULL
如果除数为零:
SELECT a / NULLIF(b, 0) FROM t
但是,我永远不会将它转换为零与coalesce
就像它显示在其他答案有很多upvotes。 这在math意义上是完全错误的,甚至是危险的,因为你的应用程序可能会返回错误和误导的结果。
SELECT Dividend / ISNULL(NULLIF(Divisor,0),1) AS Result
我写了一个函数,然后为我的存储过程处理它:
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
为非零的CHECK约束 - 将validation器添加到窗体,以便用户不能input零值到此字段。
对于更新SQL:
update Table1 set Col1 = Col2 / ISNULL(NULLIF(Col3,0),1)
没有魔术全局设置“closures0个例外”。 由于x / 0的math意义不同于NULL的含义,因此操作必须抛出,所以不能返回NULL。 我假设你正在照顾显而易见的,你的查询有条件,应该消除0除数的logging,从来没有评估师。 通常的“陷阱”比大多数开发人员所期望的SQL像程序语言的行为,并提供逻辑运算符短路,但它不是 。 我build议你阅读这篇文章: http : //www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html
使用where子句过滤掉数据,这样就不会得到0值。
这是一个你可以除以零的情况。 业务规则是计算库存周转,你花费一段时间销售商品的成本,按年计算。 在您拥有年度数字后,您将除以该期间的平均库存。
我正在计算三个月内发生的库存周转次数。 我已经计算出,在三个月的时间里,我的商品销售成本是$ 1,000。 年销售额是$ 4,000($ 1,000 / 3)* 12。 初始库存为0.结束库存为0.我的平均库存现在为0.我每年的销售额为4000美元,并且没有库存。 这产生了无限的转数。 这意味着我的所有库存都是由客户转换和购买的。
这是如何计算库存周转的业务规则。
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
当它传播callback用程序时,你可以适当地处理错误(或者如果这是你想要的,忽略它)。 在C#中,发生在SQL中的任何错误都会抛出一个exception,我可以捕捉到,然后在我的代码中处理,就像任何其他错误。
我同意贝斯卡的意见,你不想隐瞒错误。 你可能不是在处理一个核反应堆,但总体上隐藏错误是不好的编程实践。 这是大多数现代编程语言实现结构化exception处理以将实际返回值与错误/状态代码分离的原因之一。 当你在做math时尤其如此。 最大的问题是,你不能区分一个正确计算的0被返回或0作为一个错误的结果。 相反,任何返回的值都是计算值,如果出现任何错误,则抛出exception。 这当然会有所不同,具体取决于您访问数据库的方式以及您使用的是哪种语言,但是您应该始终能够获得可处理的错误消息。
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. }
使用NULLIF(exp,0)
但以这种方式 – NULLIF(ISNULL(exp,0),0)
如果exp为null
NULLIF(exp,0)
会中断,但NULLIF(ISNULL(exp,0),0)
不会中断