即使没有包含0的列,TSQL被零除遇到

我一直在试图理解为什么我用我的SQL查询得到一个“遇到零除”(消息8134),但我一定是错过了一些东西。 我想知道为什么在下面的具体情况下,我寻找NULLIFCASE WHEN...或类似的,因为我已经知道他们(当然可以在下面的情况下使用它们)。

我有一个计算列类似的SQL语句

 SELECT TotalSize, FreeSpace, (FreeSpace / TotalSize * 100) FROM tblComputer ...[ couple of joins ]... WHERE SomeCondition = SomeValue 

用上面提到的错误信息运行这个语句错误,本身并不是问题 – 显然TotalSize可能是0,因此会导致错误。

现在我不明白的是,当我将计算列注释掉时,我没有任何TotalSize 0的行,我仔细检查过,情况并非如此。

然后我认为出于某种原因,列计算将在整个结果集上执行, 然后用where子句的条件进行实际筛选,但是这样做a)在尝试重现错误时没有意义。testing设置一切工作正常(见下文):

 INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0001',1) INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0002',1) INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0003',1) INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0004',0) INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0005',1) INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0006',0) INSERT INTO tblComputer (ComputerName, IsServer) VALUES ('PC0007',1) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (1,100,21) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (2,100,10) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (3,100,55) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (4,0,10) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (5,100,23) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (6,100,18) INSERT INTO tblHDD (ComputerID, TotalSize, FreeSpace) VALUES (7,100,11) -- This statement does not throw an error as apparently the row for ComputerID 4 -- is filtered out before computing the (FreeSpace / TotalSize * 100) SELECT TotalSize, FreeSpace, (FreeSpace / TotalSize * 100) FROM tblComputer JOIN tblHDD ON tblComputer.ID = tblHDD.ComputerID WHERE IsServer = 1 

我很难过,想知道原因是什么。

任何想法或指向正确的方向非常受欢迎,提前感谢

更新

谢谢你的意见,但不幸的是,我似乎没有接近问题的根源。 我成功地删除了一点声明,现在有了这样的情况下,如果一个JOIN被删除(我将需要它为我暂时删除的输出中的其他列)我可以执行没有错误。

我不明白,为什么使用JOIN导致错误,不应该标准INNER JOIN总是返回相同的行数或更less ,但从来没有更多

工作代码

 SELECT TotalSize, FreeSpace ((FreeSpace / TotalSize) * 100) FROM MyTable1 INNER JOIN MyTable2 ON MyTable1.ID = MyTable2.Table1ID WHERE SomeCondition 

导致代码错误

 SELECT TotalSize, FreeSpace ((FreeSpace / TotalSize) * 100) FROM MyTable1 INNER JOIN MyTable2 ON MyTable1.ID = MyTable2.Table1ID -- This JOIN causes "divide by zero encountered" error INNER JOIN MyTable3 ON MyTable2.ID = MyTable3.Table2ID WHERE SomeCondition 

我也用游标试了一下我的运气,并逐行循环遍历结果,但是在这种情况下没有发生错误(不pipe上面两个语句中的哪一个尝试了)。

对不起,对于凌乱的代码缩进,不知何故正确的格式似乎并没有被应用。

G。

SQL是一种声明性语言; 您编写了一个逻辑上描述所需结果的查询,但是由优化器来生成物理计划。 这个物理计划可能与查询的书面forms没有太大的关系,因为优化器不是简单地对从查询的文本forms派生的“步骤”进行重新sorting,而是可以应用300多个不同的转换来find有效的执行策略。

优化器有相当大的自由来重新sortingexpression式,连接和其他逻辑查询结构。 这意味着,一般来说,你不能依靠任何书面的查询forms来迫使一件事先被评估。 特别是,由Lieven给出的重写不会强制在expression式之前评估WHERE子句谓词。 根据成本估算,优化器可能会决定评估expression式,看起来效率最高。 这在某些情况下甚至可能意味着expression式被多次评估。

原来的问题考虑了这个可能性,但是拒绝这个可能性不大。 尽pipe如此,这是产品的工作方式 – 如果SQL Server估计连接将减小设置的大小足以使计算连接结果的expression式变得更便宜,那么可以这样做。

一般规则是不要依赖特定的评估顺序来避免诸如溢出或零除错误之类的事情。 在这个例子中,可以使用CASE语句来检查零除数 – 防御性编程的一个例子。

优化器对事物进行重新sorting的自由是其devise的基本原则。 你可以find导致反直觉行为的案例,但是总体来说,这些好处远远超过了这些缺点。

保罗

SQL Server用来处理单个SELECT语句的基本步骤包括以下内容

  1. parsing器扫描SELECT语句并将其分解为逻辑单元,如关键字,expression式,运算符和标识符。
  2. 一个查询树(有时称为序列树)被构build,用于描述将源数据转换为结果集所需格式所需的逻辑步骤。
  3. 查询优化器分析可以访问源表的不同方式。 然后select一系列步骤,在使用较less资源的同时最快返回结果。 查询树被更新以logging这个确切的一系列步骤。 查询树的最终优化版本称为执行计划。
  4. 关系引擎开始执行执行计划。 由于需要来自基表的数据的步骤被处理,关系引擎请求存储引擎从关系引擎请求的行集传递数据。
  5. 关系引擎将从存储引擎返回的数据处理成为结果集定义的格式,并将结果集返回给客户端。

我对事物的解释是,在评估所有行的计算列之前, 不能保证 where子句得到评估。

您可以通过改变您的查询来validation该假设,如下所示,并强制在计算之前对where子句进行评估。

 SELECT TotalSize, FreeSpace, (FreeSpace / TotalSize * 100) FROM ( SELECT TotalSize, FreeSpace, FROM tblComputer ...[ couple of joins ]... WHERE SomeCondition = SomeValue ) t 

运行时将返回哪些行:

 SELECT TotalSize FROM tblComputer ...[ couple of joins ]... WHERE SomeCondition = SomeValue and ((TotalSize * 100) = 0) 

这可能会给你一个关于SQL Serve如何评估(TotalSize * 100)为零的线索。

另一个想法是,你的哪里有什么可能也是问题?
你假设它是TotalSize,但它可能在别的地方。

我遇到了同样的问题。 在我的情况下NULL是可以接受的,所以我能够这样解决它:

 Select Expression1 / Expression2 -- Caused Division By 0 Select Expression1 / NULLIF(Expression2,0) -- Causes result to be NULL 

如果你需要其他处理,你可以把整个expression式换成一个ISNULL函数,如下所示:

 Select ISNULL(Expression1 / NULLIF(Expression2,0)-5) -- Returns -5 instead of null or divide by 0