【问题标题】:Why doesn't this windowed expression result in a divide by zero error?为什么这个窗口表达式不会导致除以零错误?
【发布时间】:2019-08-01 13:21:01
【问题描述】:

我遇到了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


    【解决方案1】:

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


    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.。这个具体案例在CASEhere的上下文中提到。编译时常量折叠破坏了 CASE 的语义,这在最近的版本中得到了修复。

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

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

    【讨论】:

      【解决方案2】:

      这是有根据的推测。

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

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

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

      我会将第一个查询写成:

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

      对于exists,我使用select 1

      【讨论】:

        【解决方案3】:

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

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

        你也可以检查一下。

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

        【讨论】:

        • 有趣的是,您的查询确实没有返回错误,但是当我将order by 1/0 添加到返回多行的查询中时,它确实出错了。
        • @HoneyBadger 即使您从空表中选择并按 1/0 排序,它也会抛出除以 0 错误
        • @t-clausen.dk 我猜优化器甚至足够聪明,可以在知道只会返回一行时跳过订单。
        • @HoneyBadger 这不是全部真相,当从表中选择一行时,按 1/0 排序 将失败
        • @t-clausen.dk,是的,我的意思是当没有FROM 子句时,只能返回一行。这在编译时是已知的。当您从表中选择时,优化器可以假设只返回一行,但它不能知道
        【解决方案4】:

        ORDER BY 中不允许使用整数 literals,但允许 表达式 返回整数 - 否则您将无法使用例如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
        

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

        【讨论】:

        • 我怀疑是这种情况。我完全同意你的最后一句话!
        • @Damien_The_Unbeliever 。 . .我不认为这个答案是完全正确的。整数表达式不起作用,因此1 + 0 会导致错误。
        • 是的,但绝对禁止使用整数文字。您当然可以在整数上使用 some 表达式(最简单的情况,int 类型的列)。现在我们正在尝试选择哪些东西可以/将欺骗优化器。
        猜你喜欢
        • 2013-12-04
        • 1970-01-01
        • 2022-11-14
        • 1970-01-01
        • 1970-01-01
        • 2010-11-25
        • 2023-04-10
        • 1970-01-01
        • 2012-05-04
        相关资源
        最近更新 更多