UPDATE语句处理WHERE子句应该消除的记录

Jam*_*mes 3 sql-server-2008 sql-server update errors

我收到一个非常奇怪的错误。考虑下表:

CREATE TABLE #MyTable (
    Key1 INT , Key2 INT ,
    x SMALLINT , y INT , z INT ,
    a FLOAT , b FLOAT , c SMALLINT , s FLOAT
)

-- insert many records

CREATE UNIQUE CLUSTERED INDEX CI ON #MyTable ( Key1 , Key2 )
Run Code Online (Sandbox Code Playgroud)

出于某种原因,以下更新语句尝试除以零。这只能在c=0或 时发生c=1。该WHERE条款明确规定c>1

-- this fails with divide-by-zero error
UPDATE  #MyTable
SET s = CASE
        WHEN a - SQUARE ( b ) / c <= 0 THEN 0
        ELSE ( a - SQUARE ( b ) / c ) / ( c - 1 )
    END
WHERE   x <= 622 AND c > 1 AND ( y > 0 OR z > 0 )
Run Code Online (Sandbox Code Playgroud)

如果我c<=1CASE表达式中进行冗余检查,问题就完全消除了:

-- this completes without an error
UPDATE  #MyTable
SET s = CASE
        WHEN ( c <= 1 ) OR ( a - SQUARE ( b ) / c <= 0 ) THEN 0
        ELSE ( a - SQUARE ( b ) / c ) / ( c - 1 )
    END
WHERE   x <= 622 AND c > 1 AND ( y > 0 OR z > 0 )
Run Code Online (Sandbox Code Playgroud)

有没有人遇到过这个?为什么 SQL Server 会使用c>1?

如果表上没有索引(索引在过程后面的步骤中很有用),也可以避免该问题。为什么索引的存在会导致WHERE子句中的条件被忽略?

Aar*_*and 6

您不应该对 SQL Server如何处理您的查询做出任何假设,除非:您应该始终假设 SQL Server可以以不同于它在屏幕上显式写入的方式处理您的查询。而且这种行为也可以根据任何可能影响新计划是否将用于下一次执行甚至相同查询的因素而改变,因此如果您应用提示或以任何方式更改查询或添加或删除index 并且错误消失了,不要假设错误明天不会回来。

在这种情况下,SQL Server 在从WHERE子句中删除行之前正在处理计算。避免这种情况的方法是,就像你说的,确保这些行也在CASE表达式(不是语句)中被过滤掉。

一种更常见但类似的方法是这种事情:

SELECT DATEPART(MONTH, varchar_column)
FROM dbo.some_table
WHERE ISDATE(varchar_column) = 1;
Run Code Online (Sandbox Code Playgroud)

在许多情况下,您会收到一条错误消息,因为 SQL Server 尝试对列中的某些值应用日期函数,这些值不是日期(并且此尝试发生在过滤之前)。解决方法很乏味 - 使用CASE表达式 - 但除非您有其他方法来验证列的价值(例如,计算列或首先修复数据类型),否则这是必要的。请记住,在某些情况下,即使这样也可能无法“短路”

SELECT CASE WHEN ISDATE(varchar_column) = 1 
  THEN DATEPART(MONTH, varchar_column) END
FROM dbo.some_table
WHERE ISDATE(varchar_column) = 1;
Run Code Online (Sandbox Code Playgroud)

Erland Sommarskog 在以下反馈项目中对此进行了更彻底的解释: