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

问题描述 投票:9回答:4

我在编程拼图和Code Golf上遇到了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列时,自然)。那么为什么当一个整数不允许时允许这样做呢?

sql sql-server window-functions divide-by-zero
4个回答
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从树中删除,因为不再引用。因此,它在运行时不会产生任何问题,因为它甚至不存在于最终的执行计划中。


6
投票

你不允许在这个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

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


1
投票

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

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

你也可以检查一下。

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

1
投票

这是一个明智的推测。

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

在执行阶段生成的除零错误不会被忽略。

就个人而言,我永远不会在任何真实的代码(仅演示代码)中使用这样的非感性表达。这会在大多数其他数据库中返回除零。

我会把第一个查询写成:

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

对于exists,我使用select 1

© www.soinside.com 2019 - 2024. All rights reserved.