为什么该窗口表达式不会导致被零除错误?

时间:2019-03-11 09:00:30

标签: sql sql-server window-functions divide-by-zero

我遇到了this编程难题和代码高尔夫球的答案。在其中,作者使用了表达式(尽管此后已将答案编辑为使用其他解决方案):

row_number()over(order by 1/0)

我本来希望1/0导致被零除的异常,但事实并非如此。

当我在PPCG上向作者询问时,他们回答“因为未计算1/0。where exists(select 1/0)的作用相同”。这使我有些不高兴,因为where exists(select 1)是有效的语法,而row_number()over(order by 1)不是。那么为什么不计算order by中的表达式呢?表达式的结果是整数,order by中不允许使用整数。在这种情况下,SQL Server如何处理order by?我认为效果与row_number()over(order by (SELECT NULL))相同,但是如果我们给出一个表达式,我希望该表达式可以被求值。

巧合的是,如果有人使用类似的东西:

SELECT  ROW_NUMBER() OVER ( ORDER BY A.x )
FROM    (
            SELECT  *
            ,       1 / 0 x
            FROM    master..spt_values
        ) A

同样,没有报告被零除的错误(当未选择x列时,自然)。那么为什么不允许整数呢?

4 个答案:

答案 0 :(得分:6)

在此ORDER BY中不允许使用整数 literal ,但是允许 expressions 返回整数-否则,您将无法使用CASE表达式。

因此,这就是为什么不允许您OVER (ORDER BY 1)但显然允许您OVER (ORDER BY 1/0)的原因。我认为这不是您要故意编写的功能。如果您的表达式实际上依赖于行中的任何列,则绝对不允许除以零-这会产生错误:

select name,object_id,ROW_NUMBER() OVER (ORDER BY 1/(object_id-3))
from sys.objects

因此,我将其归结为“优化器足够聪明,可以意识到该表达式不依赖于任何行值,因此所有行的值都是相同的,因此我们无需对其进行计算”。用理智的语言,这会产生警告。

答案 1 :(得分:4)

让我们再尝试一些示例...


ROW_NUMBER() OVER (ORDER BY 2/1)
  

窗口函数和NEXT VALUE FOR函数不支持整数   索引作为ORDER BY子句表达式。

2/1的问题在于,它在优化过程的早期就一直折叠为2,因此与ROW_NUMBER() OVER (ORDER BY 2)相同,这是不允许的。


ROW_NUMBER() OVER (ORDER BY LOG(1))
  

窗口函数和NEXT VALUE FOR函数不支持   常量作为ORDER BY子句表达式。

再次进行常量折叠-这次的结果不是整数索引,但是SQL Server仍然不允许使用常量。


ROW_NUMBER() OVER (ORDER BY LOG(-1))

这在SQL Server的最新版本上成功完成-在旧版本的SQL Server 2008上,您将看到An invalid floating point operation occurred.。在CASE here的上下文中提到了这种特定情况。编译时常量折叠破坏了CASE的语义,并且在最新版本中已解决。

在这些错误情况下(LOG(-1)1/0)抑制常量折叠足以使它绕过上面给出错误消息的检查。但是,SQL Server仍然认识到该表达式实际上是一个常量,可以在以后进行优化(因此,您不会获得通过该表达式的结果对行进行排序的排序操作)。

在简化阶段,ROW_NUMBER ORDER BY non_folded_const_expression被简化为仅从树中删除的ROW_NUMBERnon_folded_const_expression,不再引用。因此,它甚至在最终执行计划中都不存在,因此在运行时不会引起任何问题。

答案 2 :(得分:1)

SQL Server将按表达式值的顺序转换为常量,而不是实际评估表达式。

因此,对于整数常数,它可以是正值或负值。因此,它不会给出错误。

您也可以检查此内容。

Select 'a' as [Test] order by 1/0

答案 3 :(得分:1)

这是有根据的推测。

SQL Server在查询的 compile 阶段优化常量表达式。在某些情况下,表达式中的错误(例如被零除)将被忽略,因为最终结果中可能不需要这些表达式。无论出于何种原因,该错误都不会标记为“常量”。

在执行阶段生成的

除零错误。

就我个人而言,我绝不会在任何实际代码(仅是演示代码)中使用这种无意义的表达式。在大多数其他数据库中,这将返回零除。

我将第一个查询写为:

row_number() over (order by (select null))

对于exists,我使用select 1