尽管没有包含 0 的列,但遇到 TSQL 除以零的情况
我一直试图理解为什么我的 SQL 查询遇到“被零除”(消息 8134),但我一定错过了一些东西。我想知道下面具体情况的原因,我不是寻找NULLIF
、CASE WHEN...
或类似的,因为我已经知道它们(当然可以在下面的情况下使用它们)。
我有一个带有计算列的 SQL 语句,类似于
SELECT
TotalSize,
FreeSpace,
(FreeSpace / TotalSize * 100)
FROM
tblComputer
...[ couple of joins ]...
WHERE
SomeCondition = SomeValue
使用上述错误消息运行此语句错误,这本身并不是问题 - 显然 TotalSize
很可能为 0,因此会导致错误。
现在我不明白的是,当我注释掉计算列时,我没有任何 TotalSize
列为 0 的行,我仔细检查了情况并非如此。
然后我认为由于某种原因,列计算将在整个结果集之前实际使用 where 子句的条件进行过滤,但这 a) 没有意义,恕我直言,b) 当尝试通过测试设置重现错误,一切正常(见下文):
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 不应该总是返回相同的行数或更少,但从不更多吗?
工作代码
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
我也尝试过使用光标并逐行循环结果,但在这种情况下没有发生错误(无论是哪一个)我尝试过上面的两个陈述)。
抱歉,代码缩进混乱,不知何故,似乎没有应用正确的格式。
G。
I've been trying to understand why I get a "divide by zero encountered" (Msg 8134) with my SQL query, but I must be missing something. I would like like to know the why for the specific case below, I am not looking for NULLIF
, CASE WHEN...
or similar as I already know about them (and can of course use them in a situation as the one below).
I have an SQL statement with a computed column similar to
SELECT
TotalSize,
FreeSpace,
(FreeSpace / TotalSize * 100)
FROM
tblComputer
...[ couple of joins ]...
WHERE
SomeCondition = SomeValue
Running this statement errors with the above mentioned error messages, which, in itself, is not the problem - obviously TotalSize
might well be 0 and therefore cause the error.
Now what I don't understand is that I do not have any rows where the TotalSize
column is 0 when I comment the computed column out, I double checked that this isn't the case.
Then I thought that for some reason the column computation would be performed on the whole result set before actually filtering with the conditions of the where clause, but this a) wouldn't make sense imho and b) when trying to reproduce the error with a test set-up everything works fine (see below):
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
I am quite stumped and would like to know what the reason is.
Any ideas or pointers into the right direction are very welcome, thanks in advance
Update
Thank you so far for your input, but unfortunately I seem not to be getting closer to the root of the problem. I managed to strip the statement down a little bit and now have the case that I can execute it without errors if one JOIN is removed (I would need it for additional columns in the output which I temporarily removed).
I do not understand, why using the JOIN leads to the error, shouldn't a standard INNER JOIN always either return the same number of rows or less, but never more?
Working code
SELECT
TotalSize,
FreeSpace
((FreeSpace / TotalSize) * 100)
FROM
MyTable1
INNER JOIN
MyTable2 ON
MyTable1.ID = MyTable2.Table1ID
WHERE
SomeCondition
Error causing code
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
I also tried my luck using a cursor and looping over the result row by row, but in that case no error occurred (no matter, which of the two statements above I tried).
Sorry for the messy code indentation, somehow the correct formatting doesn't seem to be applied.
G.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
SQL 是一种声明性语言;您编写一个查询,在逻辑上描述您想要的结果,但由优化器来生成物理计划。这个物理计划可能与查询的书面形式没有太大关系,因为优化器并不简单地重新排序从查询的文本形式派生的“步骤”,它可以应用 300 多种不同的转换来找到有效的执行策略。
优化器有相当大的自由度来重新排序表达式、连接和其他逻辑查询结构。这意味着一般情况下,您不能依赖任何书面查询形式来强制先评估一件事,然后再评估另一件事。特别是,Lieven 给出的重写不会强制在表达式之前评估 WHERE 子句谓词。优化器可以根据成本估计决定在看起来最有效的地方评估表达式。在某些情况下,这甚至可能意味着表达式会被计算多次。
最初的问题考虑了这种可能性,但拒绝了它,因为“没有多大意义”。然而,这就是产品的工作方式 - 如果 SQL Server 估计连接将减少集合大小,足以使计算连接结果的表达式变得更便宜,那么可以自由地这样做。
一般规则是永远不要依赖特定的求值顺序来避免溢出或被零除错误等问题。在此示例中,我们将使用 CASE 语句来检查除数是否为零 - 这是防御性编程的一个示例。
优化器自由地重新排序是其设计的基本原则。您可能会发现它会导致反直觉行为的情况,但总的来说,好处远远大于坏处。
保罗
SQL is a declarative language; you write a query that logically describes the result you want, but it is up to the optimizer to produce a physical plan. This physical plan may not bear much relation to the written form of the query, because the optimizer does not simply reorder 'steps' derived from the textual form of the query, it can apply over 300 different transformations to find an efficient execution strategy.
The optimizer has considerable freedom to reorder expressions, joins, and other logical query constructions. This means that you cannot, in general, rely on any written query form to force one thing to be evaluated before another. In particular, the rewrite given by Lieven does not force the WHERE clause predicate to be evaluated before the expression. The optimizer may, depending on cost estimations, decide to evaluate the expression wherever it seems most efficient to do so. This may even mean, in some cases, that the expression is evaluated more than once.
The original question considered this possibility, but rejected it as 'not making much sense'. Nevertheless, this is the way the product works - if SQL Server estimates that a join will reduce the set size enough to make it cheaper to compute the expression on the result of the join, it is free to do so.
The general rule is to never depend on a particular evaluation order to avoid things like overflow or divide-by-zero errors. In this example, one would employ a CASE statement to check for a zero divisor - an example of defensive programming.
The optimizer's freedom to reorder things is a fundamental tenet of its design. You can find cases where it leads to counter-intuitive behaviours, but overall the benefits far outweigh the disadvantages.
Paul
SQL Server 使用处理单个 SELECT 语句的基本步骤包括下列的
我对事情的解释是,不能保证在评估所有行的计算列之前先评估您的where子句。
您可以通过像下面这样更改查询并强制在计算之前评估 where 子句来验证该假设。
The basic steps that SQL Server uses to process a single SELECT statement include the following
My interpretation of things is that there is no guarantee that your where clause get's evaluated before evaluating the computed column for all rows.
You could verify that assumption by changing you query like below and forcing the where clause to be evaluated before the computation.
运行时返回哪些行:
这可能会为您提供有关 SQL Serve 如何将 (TotalSize * 100) 计算为零的线索。
另一个想法,您的 where 语句中是否有任何内容也可能是问题所在?
您假设它是 TotalSize,但它可能在其他地方。
What rows are returned when you run:
This might give you a clue as to how SQL Serve ris evaluating (TotalSize * 100) to be zero.
Another idea, is there anything in your where statement which might also be the problem?
You're assuming it's the TotalSize, but it might be somewhere else.
我遇到了同样的问题。在我的例子中,NULL 是可以接受的,所以我能够通过这种方式修复它:
如果您需要其他处理,您可以将整个表达式包装在 ISNULL 函数中,如下所示:
I was running into the same issue. In my case NULLs were acceptable so I was able to fix it this way:
If you need other handling, you can wrap the entire expression in an ISNULL function like this: