ChatGPT解决这个技术问题 Extra ChatGPT

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

我有这个错误信息:

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

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

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

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

或者

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

使用 NULLIF 子句的最佳方式是什么?

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

也许一些数据验证是有序的。

H
Henrik Staun Poulsen

为了避免“除以零”错误,我们将其编程如下:

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

但这里有一个更好的方法:

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

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


一个更好的方法“选择除数/nullif(除数,0)......”如果除数为NULL,则中断。
@Anderson这根本不是真的。您确定您没有不小心使用 IsNull 而不是 NullIf?自己试试吧! SELECT Value,1/NullIf(Value,0)FROM(VALUES(0),(5.0),(NULL))x(Value); 除非“中断”,否则您的意思是返回 NULL?您可以使用 IsNullCoalesce 将其转换为您想要的任何内容。
@ErikE,这是真的......尝试运行......选择1 / nullif(null,0)......你得到“NULLIF的第一个参数的类型不能是NULL常量,因为第一个参数的类型有要知道。”使用 "coalesce(FieldName,0)" 处理此问题...例如 select 1/nullif(coalesce(null,0),0)
@JohnJoseph 我不知道您是同意我的观点还是与我争论。
@JohnJoseph 仔细看看你得到的错误。是的,SELECT 1 / NULLIF(NULL, 0) 失败,但这是因为 NULLIF() 需要知道第一个参数的数据类型。这个更改后的示例运行良好:SELECT 1 / NULLIF(CAST(NULL AS INT), 0)。在现实生活中,您将为 NULLIF() 而不是 NULL 常量提供表列。由于表列具有已知的数据类型,这也可以正常工作:SELECT 1 / NULLIF(SomeNullableColumn, 0) FROM SomeTable
P
Peter Mortensen

如果您想返回零,以防发生零偏差,您可以使用:

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

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


一些基准测试表明 COALESCE 比 ISNULL 稍慢。但是,COALESCE 在标准中,因此更便携。
如果其他人没有立即明白为什么会这样,如果 d 为 0,NULLIF(d,0) 将返回 NULL。在 SQL 中,除以 NULL 返回 NULL。 Coalesce 将生成的 NULL 替换为 0。
@SQLGeorge虽然我同意您的论点,但请注意,在某些情况下,人们更关心统计上的正确性而不是数学上的正确性。在某些使用统计函数的情况下,当除数为零时,0 甚至 1 都是可接受的结果。
有人可以向我解释为什么这很糟糕吗?如果我想计算一个百分比并且除数为零,我当然希望结果为零。
我认为@George 和@James/ Wilson 从根本上误解了所提出的问题。当然,在某些业务应用程序中返回“0”是合适的,即使从数学角度来看它在技术上并不正确。
K
KenD

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

假设您要计算各个学校俱乐部的男女比例,但您发现以下查询失败并在尝试计算没有女性的魔戒俱乐部的比例时出现除零错误:

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;

任何数除以 NULL 得到 NULL,不会产生错误。


是的,确实,这比其他获得如此多支持的答案要好得多。在您的解决方案中,您至少有一个 NULL,这表明您无法提供正确的结果。但是,如果您将结果从 NULL 转换为零,那么您只会得到错误和误导性的结果。
顺便说一句,如果您想计算男女比例,那么我建议您最好将其与总数进行比较,例如:select males/(males+females), females/(males+females)。这将为您提供俱乐部中男性和女性的百分比分布,例如 31% 的男性和 69% 的女性。
r
rmrrm

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

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

因此,如果您有类似 100/0 的内容,它将返回 NULL。我只为简单的查询做了这个,所以我不知道它会如何影响更长/复杂的查询。


为我工作。就我而言,我必须在 WHERE 子句中使用除法操作。我确定没有零分隔符,因为当我注释 WHERE 时,结果中没有零值。但不知何故,查询优化器在过滤时确实除以零。 SET ARITHABORT OFF SET 和 ANSI_WARNINGS OFF 可以工作 - 经过 2 天的战斗,在 WHERE 子句中除以零。谢谢!
这“感觉”很脏,但我喜欢它!在进行聚合的查询中需要它并使用 CASE 语句不是一个选项,因为那时我必须将该列添加到 GROUP BY 中,这完全改变了结果。使初始查询成为子选择,然后对外部查询执行 GROUP BY 也会更改结果,因为涉及除法。
好的,所以我仍然喜欢这个“解决方案”,但就像你们中的许多人可能觉得的那样,我觉得必须有一个“更清洁”的方式。如果我忘记重新启用警告怎么办?或者有人克隆了我的代码(这永远不会发生,对吗?)并且没有考虑警告?无论如何,看到了关于 NULLIF() 的其他答案。我知道 NULLIF() 但没有意识到除以 NULL 返回 NULL(我认为这将是一个错误)。所以...我选择了以下内容: ISNULL( (SUM(foo) / NULLIF(SUM(bar),0) ), 0) AS Avg
我不知道这个解决方案。我不确定我是否喜欢它,但有一天知道它可能会有用。非常感谢。
这是最简单的解决方案,但请注意它会损害性能。来自 docs.microsoft.com/en-us/sql/t-sql/statements/…:“将 ARITHABORT 设置为 OFF 会对查询优化产生负面影响,从而导致性能问题。”
S
SQL Police

如果除以零,您至少可以阻止查询中断并返回 NULL

SELECT a / NULLIF(b, 0) FROM t 

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


如果在程序中使用,您的担忧是有效的。如果我们用百分比向管理层提交报告怎么样?我认为使用 this: SUM(Charge_Amount) OVER(Partition BY Charge_Date) AS Total_Charge_Amount,Charge_Amount/ISNULL((NULLIF(SUM(Charge_Amount) OVER(Partition BY Charge_Date),0)),1)*100 AS pct 如果仅在 SELECT 上下文中使用是合适的。 Charge_Amount 在某些 Charge_Date 中出现 0.00,因此 sum 在少数情况下也是 0,我们得到一个错误。请让我知道你的想法。谢谢
@Heike,如果您想向管理层显示报告,则向他们显示类似 #NA 的内容,但不要显示“0%”,因为这会给人一种错误的印象,即两个列是相同的(例如,如果您想比较实际数字与预算数字)
N
Nisarg Shah
SELECT Dividend / ISNULL(NULLIF(Divisor,0), 1) AS Result from table

通过用 nullif() 捕捉零,然后用 isnull() 捕捉到的 null,您可以避免除以零错误。


由于篇幅较长,建议您删除您的答案。请注意,最好对您的建议添加一个小的解释——即使它看起来很简单;)
B
Beska

编辑:我最近对此有很多反对意见......所以我想我只是添加一个注释,这个答案是在问题经历它最近的编辑之前写的,其中返回 null 被突出显示为一个选项.. .这似乎很可接受。在评论中,我的一些回答是针对像 Edwardo 这样的担忧,他似乎主张返回 0。这就是我所反对的情况。

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

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

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

想象一下,我正在编写一些东西,但我把它搞砸了。我应该读取辐射测量缩放值,但在一个我没有预料到的奇怪边缘情况下,我读取了 0。然后我将我的值放入你的函数中......你给我一个 0!万岁,无辐射!除了它真的在那里,只是我传递了一个错误的价值......但我不知道。我希望除法抛出错误,因为它是出现问题的标志。


我不同意。您的业务规则永远不应该以非法计算而告终。如果你最终做了这样的事情,你的数据模型很可能是错误的。每当您遇到除以 0 时,您应该考虑数据是否应该为 NULL 而不是 0。
我不敢相信有人问我是否曾经“做过任何真正的编程?”因为我说要做对,而不是偷懒。叹
对不起,我不是故意要冒犯你的。但是这个问题在许多常见的 LOB 应用程序中是完全有效的,并且用“除以 0 是不合法的”来回答它并没有增加价值恕我直言。
@JackDouglas 对。在这种情况下,您希望业务规则以特殊方式处理特殊情况......但它不应该是返回空白的基础数学。这应该是商业规则。接受的答案返回一个空值,这是处理它的好方法。抛出异常也可以。在它进入 SQL 之前发现并处理它可以说是理想的。提供某种其他东西可以调用的函数返回数学上不正确的值并不是可行的方法,因为这种特殊情况可能不适用于其他调用者。
@JackDouglas 是的,这是一个很好的总结,我同意。最初,这个问题似乎被表述为“我能做些什么来隐藏这个错误”。从那时起,它已经发展。返回一个空值,他最终得出的答案,似乎是一个合理的回应。 (我强烈主张不要返回 0 或其他数字。)
N
N Mason

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

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

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

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

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


@N梅森;是的,有时 1 是一个选项。但是,当您键入反斜杠时,您如何记住 ISNULL 部分?
抱歉 Henrik - 我不确定我是否理解这个问题。
P
Peter Mortensen

不久前我写了一个函数来为我的 stored procedures 处理它:

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

嗨,Ron,很好的解决方案,除了它的数据类型有限(小数点后 4 位)而且我们的 @divisors 也可以是负数。你如何强制使用它? TIA Henrik Staun Poulsen
当时我很快就处理了一个特定的问题场景。单一开发者应用程序,所以除了我的记忆之外,执行起来并不困难。 :-)
尽管有 print 语句,但它不是存储过程,而是标量 UDF。如果它是查询的一部分,这将在 MS-SQL 中杀死你。
我同意 Mark Sowul 的断言,即标量函数会引起疼痛。这是 T-SQL 中的一个糟糕建议,不要这样做!标量函数是性能破坏者!内联表值函数是 SQL Server 中唯一好的用户函数(可能除了可以很好执行的 CLR 函数)。
您可以将整个函数替换为表达式 @numerator / NULLIF(@divisor, 0)。它的作用相同,而且速度更快。
f
finnw

添加强制除数为非零的 CHECK 约束 向表单添加验证器,以便用户无法在此字段中输入零值。


我开始越来越喜欢 CHECK 约束。
V
Vijay Bansal

对于更新 SQL:

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

嗨,维杰,是的,这会起作用,但是......我会小心 ISNULL 部分,你最终会除以 NULL。我宁愿告诉用户结果未知,因为除数为零。
它在复杂的子查询中救了我,谢谢。
R
Remus Rusanu

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


有这样一个“魔术全局设置”;SET ARITHABORT OFF。
J
Jimmy

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

我正在研究计算三个月内发生的库存周转次数。我已经计算出我在三个月内销售的商品成本为 1,000 美元。年销售额为 4,000 美元(1,000 美元/3)*12。期初库存为 0。期末库存为 0。我现在的平均库存为 0。我的年销售额为 4000 美元,没有库存。这会产生无限数量的转弯。这意味着我所有的库存都被客户转换和购买。

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


是的,然后你有无限数量的回合。因此,在这种情况下,如果您有除以零,那么您应该显示类似“#INF”的内容。
“期初库存为 0。期末库存为 0。我的平均库存现在为 0。”你的计算是一个估计。有时库存是正数,或者您无法运送/出售任何东西。如果您对 +∞ 结果不满意,请使用更好的平均库存估算值。
G
Gregory Hart
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 中都有这个。
对我来说,这个解决方案是完美而优雅的
@Payedimaunt;是的,UDF 会导致非常优雅的代码。但它的表现并不好。这是“安眠药上的光标”。 :-) 也很难记住写 dbo.Divide 而不是普通的“/”
n
nunespascal

使用 where 子句过滤掉数据,这样您就不会得到 0 值。


J
Joeyslaptop

有时,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

我发现问题是当你想做一个除法的时候要记住做一些事情。如果您忘记添加 CASE 或 NULLIF,您将在 x 周后的周一早上收到支持案例。我讨厌那个。
D
Dale K

这就是我修复它的方法:

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

它可以包含在更新中:

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

或在选择中:

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

想法?


我和其他人发现用零替换“未知”是一种危险的解决方法。我更喜欢NULL。但困难的一点是要记住在所有分区上添加 iif 或 nullif !
D
Despertar

当错误传播回调用程序时,您可以适当地处理错误(或者如果这是您想要的,则忽略它)。在 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.
}

我想我们都同意用 0 隐藏错误不是解决方案。我的建议是编写我们的代码,使每个“/”后跟一个“NULLIF”。这样,我的报告/核反应堆就不会被单独留下,而是显示“NULL”而不是那个讨厌的错误消息 8134。当然,如果我在除数为 0 时需要不同的进程,那么我和 Beska 同意。我们需要编写代码。每次写“/”时都很难做到。 “/NULLIF”也不是每次都做不到。
H
Henrik Staun Poulsen

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

如果 exp 为 null,则 NULLIF(exp,0) 中断,但 NULLIF(ISNULL(exp,0),0) 不会中断


我不能得到 NULLIF(exp, 0),如果 0 是零,而不是 o。尝试; DECLARE @i INT SELECT 1 / NULLIF(@i, 0) 看看你是否可以让它中断。