Sql-Server

UPDATE 語句處理應被 WHERE 子句消除的記錄

  • October 28, 2021

我收到一個非常奇怪的錯誤。考慮下表:

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 )

出於某種原因,以下更新語句嘗試除以零。這只有在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 )

c<=1如果我在CASE表達式中進行冗餘檢查,問題就完全消除了:

-- 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 )

有沒有人遇到過這個?為什麼 SQL Server 會用 觸及記錄c>1

如果表上沒有索引,也可以避免該問題(索引在過程後面的步驟中很有用)。為什麼索引的存在會導致WHERE子句中的條件被忽略?

您不應該對 SQL Server將如何處理您的查詢做出任何假設,除了以下幾點:您應該始終假設 SQL Server可以以不同於在螢幕上顯式寫入的方式處理您的查詢。而且這種行為可能會根據任何可能影響是否將新計劃用於下一次執行甚至相同查詢的因素而改變,因此,如果您應用提示或以任何方式更改查詢或添加或刪除index 並且錯誤消失了,不要假設錯誤明天不會再出現。

WHERE在這種情況下,SQL Server 在從子句中刪除行之前正在處理計算。正如您所說,避免這種情況的方法是確保在CASE表達式(而不是語句)中也過濾掉這些行。

一種更常見但類似的方法是這種事情:

SELECT DATEPART(MONTH, varchar_column)
FROM dbo.some_table
WHERE ISDATE(varchar_column) = 1;

在許多情況下,您會收到一條錯誤消息,因為 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;

Erland Sommarskog 在以下回饋項目中對此進行了更徹底的解釋:

引用自:https://dba.stackexchange.com/questions/115948