為什麼添加 WHERE 子句會破壞我的工作查詢,並出現錯誤“查詢處理器無法生成查詢計劃,因為定義了提示……”?
我有以下(愚蠢地簡化)查詢,它利用兩個 CTE 引用同一個表並將它們相互連接:
WITH CTE1 AS ( SELECT dbo.RemoveNonNumericCharacters(PhoneNumber) AS PhoneNumberCleaned FROM PhoneNumbersTable GROUP BY dbo.RemoveNonNumericCharacters(PhoneNumber) ), CTE2 AS ( SELECT CTE1.PhoneNumberCleaned FROM CTE1 INNER HASH JOIN PhoneNumbersTable ON CTE1.PhoneNumbersCleaned = dbo.RemoveNonNumericCharacters(PhoneNumbersTable.PhoneNumber) WHERE PhoneNumbersTable.AreaCode IN (718, 212) ) SELECT PhoneNumberCleaned FROM CTE2
注意
HASH JOIN
裡面發生的事情CTE2
。到目前為止,這一切都運作良好。如果我將以下
WHERE
子句添加到最終SELECT
查詢中,那麼我的整個查詢現在變為:WITH CTE1 AS ( SELECT dbo.RemoveNonNumericCharacters(PhoneNumber) AS PhoneNumberCleaned FROM PhoneNumbersTable GROUP BY dbo.RemoveNonNumericCharacters(PhoneNumber) ), CTE2 AS ( SELECT CTE1.PhoneNumberCleaned FROM CTE1 INNER HASH JOIN PhoneNumbersTable ON CTE1.PhoneNumbersCleaned = dbo.RemoveNonNumericCharacters(PhoneNumbersTable.PhoneNumber) WHERE PhoneNumbersTable.AreaCode IN (718, 212) ) SELECT PhoneNumberCleaned FROM CTE2 WHERE PhoneNumberCleaned = 'SomePhoneNumberInTheResultSet' -- E.g. 7183998888
然後我得到經典錯誤:
消息 8622,級別 16,狀態 1,第 50 行 由於此查詢中定義的提示,查詢處理器無法生成查詢計劃。在不指定任何提示且不使用 SET FORCEPLAN 的情況下重新送出查詢。
僅當我在
WHERE
子句中使用的值實際存在於結果集中時,才會發生這種情況。如果我選擇任何不存在的值,那麼我不會收到上述錯誤。現在顯然我的例子對於正在發生的事情有點愚蠢,我可以用幾種不同的方式重寫它來修復它,但我更好奇*為什麼會發生這種情況?*如果 SQL Server 引擎能夠生成返回所有記錄的查詢計劃,為什麼它無法在該查詢計劃的末尾為我在
WHERE
子句中過濾的標量值添加額外的過濾運算符?這是我的函式的黑盒程式碼
dbo.RemoveNonNumericCharacters
(注意我沒有寫這個):CREATE FUNCTION [dbo].[RemoveNonNumericCharacters] (@strText VARCHAR(1000)) RETURNS VARCHAR(1000) AS BEGIN WHILE PATINDEX('%[^0-9]%', @strText) > 0 BEGIN SET @strText = STUFF(@strText, PATINDEX('%[^0-9]%', @strText), 1, '') END RETURN @strText END
另請注意, 中的列
PhoneNumber
是PhoneNumbersTable
類型VARCHAR(20)
。
該問題不包含複製腳本,但由於隱含謂詞使連接謂詞變得多餘,因此經常出現此錯誤。
換句話說,查詢規範中的邏輯含義將內部聯接變成了邏輯叉積(簡化後)。這不一定是一件壞事(因為人們傾向於假設交叉產品是)它只是意味著可以以這種方式簡化查詢規範。
雜湊連接需要一個相等謂詞。在優化器考慮連接實現時,如果查詢樹中沒有合適的謂詞,編譯會失敗並出現錯誤。在理想情況下,優化器可能不會簡化滿足提示所需的連接謂詞。
這是我根據問題的文本內容做出的有根據的猜測。如果需要更詳細的解釋,請提供完整的repro並指定環境。
作為一個附帶問題,除了 Erik Darling 的建議之外,這裡有一個適用於 SQL Server 2016的確定性純數字標量函式:
CREATE FUNCTION dbo.RemoveNonNumericCharacters (@string nvarchar(4000)) RETURNS nvarchar(4000) WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT AS BEGIN RETURN ISNULL( CONVERT(nvarchar(4000), ( SELECT This.ch AS [text()] FROM OPENJSON(N'[1' + REPLICATE(N',1', LEN(@string) - 1) + N']') AS J CROSS APPLY (SELECT 1 + CONVERT(integer, J.[Key])) AS V (v) OUTER APPLY (SELECT SUBSTRING(@string COLLATE Latin1_General_100_BIN2, V.v, 1)) AS This (ch) WHERE This.ch COLLATE Latin1_General_100_BIN2 LIKE N'[0123456789]' ORDER BY V.v FOR XML PATH (N'') )), N''); END;
這可能不是效率的最後一句話,但它很有趣。您可以使用
CHARINDEX
.對於 SQL Server 2017 及更高版本:
CREATE FUNCTION dbo.RemoveNonNumericCharacters (@string nvarchar(4000)) RETURNS nvarchar(4000) WITH SCHEMABINDING, --INLINE = ON, /* for 2019 */ RETURNS NULL ON NULL INPUT AS BEGIN RETURN REPLACE( TRANSLATE(@string COLLATE Latin1_General_100_BIN2, TRANSLATE(@string COLLATE Latin1_General_100_BIN2, N'0123456789', N'XXXXXXXXXX'), REPLICATE(N'X', LEN(@string))), N'X', N''); END;
兩者都可以簡單地轉換為內聯表值函式。