为什么这个加窗表达式不会导致被零除错误?

Why doesn't this windowed expression result in a divide by zero error?

我遇到了 this 关于编程难题和代码高尔夫的回答。在其中,作者使用了表达式(尽管答案已被编辑为使用不同的解决方案):

row_number()over(order by 1/0)

我原以为 1/0 会导致被零除异常,但事实并非如此。

当我在 PPCG 上询问作者时,他们回答 "because 1/0 is not being calculated. where exists(select 1/0) will have the same effect"。这让我有点困惑,因为 where exists(select 1) 是有效语法,但 row_number()over(order by 1) 不是。那么为什么order by中的表达式没有被计算呢?表达式的结果是一个整数,order by中不允许有整数。在这种情况下,SQL 服务器如何处理 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 列时)。那么为什么在不允许整数的情况下允许这样做呢?

在此 ORDER BY 中不允许使用整数 文字 但可以使用 表达式 返回整数 - 否则你不会不能使用例如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

所以,我将其归结为 "the optimizer is smart enough to realise that this expression doesn't depend on any row values, therefore it's the same value for all rows so we do not need to compute it"。用理智的语言来说,这会产生警告。

SQL 服务器将按表达式值排序的顺序转换为常量,而不是实际计算表达式。

所以在整数常量的情况下,它可能是正值或负值。因此它不会给出错误。

你也可以看看这个

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

这是有根据的猜测。

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

不忽略执行阶段产生的被零除错误。

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

我会把第一个查询写成:

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

对于exists,我使用select 1

让我们再试几个例子...


ROW_NUMBER() OVER (ORDER BY 2/1)

Windowed functions and NEXT VALUE FOR functions do not support integer indices as ORDER BY clause expressions.

2/1 的问题是它在优化过程的早期被常量折叠到 2,因此被视为与 ROW_NUMBER() OVER (ORDER BY 2) 相同,这是不允许的。


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

Windowed functions and NEXT VALUE FOR functions do not support constants as ORDER BY clause expressions.

常量折叠再次启动 - 这次结果不是整数索引,但 SQL 服务器无论如何都不允许常量。


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 服务器仍然识别出该表达式实际上是一个常量并且可以在以后进行优化(因此您不会获得排序操作以根据该表达式的结果对行进行排序)。

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