我遇到了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列时,自然)。那么为什么不允许整数呢?
答案 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_NUMBER
和non_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
。