Gor*_*ger 10 t-sql calculated-columns divide-by-zero
我一直试图理解为什么我的SQL查询得到了"除以零"(Msg 8134),但我必须遗漏一些东西.我想知道为什么对于下面的具体情况,我不是在寻找NULLIF,CASE WHEN...或者与我已经了解的相似(当然可以在下面的情况下使用它们).
我有一个SQL语句与计算列类似
SELECT
TotalSize,
FreeSpace,
(FreeSpace / TotalSize * 100)
FROM
tblComputer
...[ couple of joins ]...
WHERE
SomeCondition = SomeValue
Run Code Online (Sandbox Code Playgroud)
使用上面提到的错误消息运行此语句错误,这本身不是问题 - 显然TotalSize可能是0并因此导致错误.
现在我不明白的是,TotalSize当我将计算列注释掉时,我没有任何列为0的行,我仔细检查过不是这种情况.
然后我认为由于某种原因,在使用where子句的条件进行实际过滤之前,将对整个结果集执行列计算,但是这个a)在imho和b)尝试使用a重现错误时没有意义测试设置一切正常(见下文):
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
Run Code Online (Sandbox Code Playgroud)
我很难过,想知道原因是什么.
任何提出正确方向的想法或指示都是非常受欢迎的,在此先感谢
更新
到目前为止,感谢您的意见,但不幸的是,我似乎没有更接近问题的根源.我设法将语句删除了一点点,现在有了这样的情况:如果删除了一个JOIN,我可以毫无错误地执行它(我需要它来输出我暂时删除的其他列).
我不明白,为什么使用JOIN导致错误,标准INNER JOIN不应该返回相同数量的行或更少,但从不更多?
工作代码
SELECT
TotalSize,
FreeSpace
((FreeSpace / TotalSize) * 100)
FROM
MyTable1
INNER JOIN
MyTable2 ON
MyTable1.ID = MyTable2.Table1ID
WHERE
SomeCondition
Run Code Online (Sandbox Code Playgroud)
导致代码错误
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
Run Code Online (Sandbox Code Playgroud)
我也尝试使用游标并逐行循环遍历结果,但在这种情况下没有发生错误(无论如何,上面我试过的两个语句中的哪一个).
很抱歉凌乱的代码缩进,似乎没有应用正确的格式.
G.
小智 13
SQL是一种声明性语言; 您编写的逻辑描述所需结果的查询,但优化程序可以生成物理计划.这个物理计划可能与查询的书面形式没有多大关系,因为优化器不会简单地重新排序从查询的文本形式派生的"步骤",它可以应用超过300种不同的转换来找到有效的执行策略.
优化器有相当大的自由来重新排序表达式,连接和其他逻辑查询结构.这意味着您通常不能依赖任何书面查询形式来强制在另一个之前评估一件事.特别是,Lieven给出的重写不会强制在表达式之前计算WHERE子句谓词.根据成本估算,优化程序可以决定在最有效的位置评估表达式.在某些情况下,这甚至可能意味着表达式被评估不止一次.
最初的问题考虑了这种可能性,但拒绝了它"没有多大意义".然而,这就是产品的工作方式 - 如果SQL Server估计连接将减小设置大小足以使得在连接结果上计算表达式更便宜,则可以自由地执行此操作.
一般规则是永远不要依赖于特定的评估顺序来避免溢出或被零除错误之类的事情.在这个例子中,人们会使用CASE语句来检查零除数 - 一个防御性编程的例子.
优化器重新排序的自由是其设计的基本原则.您可以找到导致反直觉行为的案例,但总体而言,其好处远大于缺点.
保罗