1. 程式人生 > 資料庫 >SQL Prompt教程:子查詢使用[NOT] EXISTS代替[NOT] IN(PE019)

SQL Prompt教程:子查詢使用[NOT] EXISTS代替[NOT] IN(PE019)

SQL Prompt是一款實用的SQL語法提示工具。SQL Prompt根據資料庫的物件名稱、語法和程式碼片段自動進行檢索,為使用者提供合適的程式碼選擇。自動指令碼設定使程式碼簡單易讀–當開發者不大熟悉指令碼時尤其有用。SQL Prompt安裝即可使用,能大幅提高編碼效率。此外,使用者還可根據需要進行自定義,使之以預想的方式工作。

在使用子查詢比較資料集時,過去曾經是EXISTS邏輯運算子比IN更快。例如,在查詢必須執行特定任務的情況下,但僅當子查詢返回任何行時,然後在評估WHERE [NOT] EXISTS(子查詢)時,資料庫引擎只要發現一個就可以退出搜尋行,而WHERE [NOT] IN(子查詢) 將始終在進一步處理之前從子查詢中收集所有結果。

但是,查詢優化器現在會盡可能以相同的方式對待EXISTS和IN,因此您不太可能看到任何明顯的效能差異。但是,如果子查詢的源資料包含NULL值,則在使用NOT IN運算子時需要謹慎。如果是這樣,則應考慮使用NOT EXISTS運算子而不是NOT IN,或者將語句重鑄為左外部聯接。

SQL Prompt(PE019)中的程式碼分析規則中包含了建議使用[NOT] EXISTS而不是[NOT] IN的建議。
在這裡插入圖片描述

哪種效果更好:EXISTS或IN …?

有兩種方法可以計算出兩個資料集之間的差異,但是最常見的兩種方法是使用EXISTS或IN邏輯運算子。想象一下,我們有兩個簡單的表,一個表包含英語中的所有常用單詞(CommonWords),另一個表包含Bram Stoker的“ Dracula”中的所有單詞的列表(WordsInDracula)。該TestExistsAndIn下載包括指令碼來建立這兩個表,並填充和與之相關的文字檔案中每一個。通常,在沙盒伺服器中擁有這樣的表對於在進行開發工作時執行測試很有用,儘管您可以選擇使用的書!

在德古拉語中有多少個不常見的單詞?假設NULL該CommonWords.Word列中沒有值(稍後會詳細介紹),則以下查詢將返回相同的結果(1555個字),並具有相同的執行計劃,這在兩個之間使用了合併聯接(Right Anti Semi Join)表。
–using NOT IN
SELECT Count(*)
FROM dbo.WordsInDracula
WHERE word NOT IN (SELECT CommonWords.word FROM dbo.CommonWords);

–Using NOT EXISTS
SELECT Count(*)
FROM dbo.WordsInDracula

WHERE NOT EXISTS
(SELECT * FROM dbo.CommonWords
WHERE CommonWords.word = WordsInDracula.word);

清單1

簡而言之,SQL Server優化器以相同的方式處理任一查詢,它們也將執行相同的查詢。

…或任何其他(除內部聯接,外部聯接或相交之外)?
什麼其他所有可能的技術,但是,如使用ANY,EXCEPT,INNER JOIN,OUTER JOIN或INTERSECT?清單2顯示了我可以輕鬆想到的另外七個替代方案,儘管還有其他替代方案。
–using ANY
SELECT Count(*)
FROM dbo.WordsInDracula
WHERE NOT(WordsInDracula.word = ANY
(SELECT word
FROM commonwords )) ;
–Right anti semi merge join

–using EXCEPT
SELECT Count(*)
FROM
(
SELECT word
FROM dbo.WordsInDracula
EXCEPT
SELECT word
FROM dbo.CommonWords
) AS JustTheUncommonOnes;
–Right anti semi merge join

–using LEFT OUTER JOIN
SELECT Count(*)
FROM dbo.WordsInDracula
LEFT OUTER JOIN dbo.CommonWords
ON CommonWords.word = WordsinDracula.word
WHERE CommonWords.word IS NULL;
–right outer merge join

–using FULL OUTER JOIN
SELECT Count(*)
FROM dbo.WordsInDracula
full OUTER JOIN dbo.CommonWords
ON CommonWords.word = WordsinDracula.word
WHERE CommonWords.word IS NULL;
–Full outer join implemented as a merge join.

–using intersect to get the difference
SELECT (SELECT Count() FROM WordsInDracula)-Count()
FROM
(
SELECT word
FROM dbo.WordsInDracula
intersect
SELECT word
FROM dbo.CommonWords
) AS JustTheUncommonOnes;
–inner merge join

–using FULL OUTER JOIN syntax to get the difference
SELECT Count()-(SELECT Count() FROM CommonWords)
FROM dbo.WordsInDracula
full OUTER JOIN dbo.CommonWords
ON CommonWords.word = WordsinDracula.word
–full outer merge join

–using INNER JOIN syntax to get the difference
SELECT (SELECT Count() FROM WordsinDracula)-Count()
FROM dbo.WordsInDracula
INNER JOIN dbo.CommonWords
ON CommonWords.word = WordsinDracula.word
–inner merge join

清單2

測試線束

所有這9個查詢都給出相同的結果,但有沒有一種方法的效果更好?讓我們將它們全部放入一個簡單的測試工具中,看看每個版本需要多長時間!再次,程式碼下載檔案包括測試工具程式碼以及所有九個查詢。

結果表明,儘管查詢看起來有很大不同,但對於優化程式而言,它通常只是“語法糖”。無論您的SQL有多優雅,優化器都只會聳聳肩,並提出執行它的有效計劃。實際上,前四個都使用完全相同的“正確的半合併合併”執行計劃,並且都花費相同的時間。
在這裡插入圖片描述

我們將通過多次執行測試來檢查差異。該INTERSECT和INNER JOIN查詢中都使用內部合併連線,並接近。這兩個FULL OUTER JOIN查詢稍慢一些,但這是一場激烈的比賽。
在這裡插入圖片描述

NOT IN的陷阱

比較具有空值的集合存在一定的不現實性,但是如果在每天的資料庫報告熱中發生這種情況,則可能會出錯。如果NULL子查詢或表示式的結果中有一個值傳遞給IN邏輯運算子,則它將給出合理的響應,並且與等效值相同EXISTS。但是,NOT IN行為卻大不相同。

清單3演示了這個問題。我們在@someWord表變數中插入三個常用詞和三個不常用詞,並且我們想知道不在表變數中的常用詞的數量。
SET NOCOUNT ON;
DECLARE @someWord TABLE
(
word NVARCHAR(35) NULL
);
INSERT INTO @someWord
(
word
)
–three common words
SELECT TOP 3
word
FROM dbo.commonwords
ORDER BY word DESC;

– three uncommon words
INSERT INTO @someWord
(
word
)
VALUES
(‘flibberty’),
(‘jibberty’),
(‘flob’);

SELECT [NOT EXISTS without NULL] = COUNT(*)
FROM commonwords AS MyWords
WHERE NOT EXISTS
(
SELECT word FROM @someWord AS s WHERE s.word LIKE MyWords.word
);

SELECT [NOT IN without NULL] = COUNT(*)
FROM commonwords AS MyWords
WHERE word NOT IN (
SELECT word FROM @someWord
);

–Insert a NULL value
INSERT INTO @someWord
(
word
)
VALUES
(NULL);

SELECT [NOT EXISTS with NULL] = COUNT()
FROM commonwords AS MyWords
WHERE NOT EXISTS
(
SELECT word FROM @someWord AS s WHERE s.word LIKE MyWords.word
);
SELECT [NOT IN with NULL] = COUNT(
)
FROM commonwords AS MyWords
WHERE word NOT IN (
SELECT word FROM @someWord
);

清單3

在NOT IN查詢時,才插入NULL到@someword,並且兩個NOT EXISTS查詢,所有正確地告訴我們,60385點的話是不是在我們的表變數,因為三都,並有在所有60388個常用詞。但是,如果子查詢可以返回NULL,則NOT IN根本不返回任何行。

在這裡插入圖片描述

NULL真正的意思是“未知”而不是什麼,這就是為什麼任何與NULL值比較的表示式都會返回NULL或未知。
從邏輯上講,SQL Server評估子查詢,將其替換為其返回的值列表,然後評估[NOT] IN條件。對於IN我們查詢的變體,這不會引起問題,因為它可以解決以下問題:
WHERE word = ‘flibberty’ OR word = ‘jibberty’ OR word = ‘flob’
OR word = ‘zygotes’ OR word = ‘zygote’ OR word = ‘zydeco’
OR word = NULL;
對於“ z…”字樣的匹配項,將返回3行。附帶了刺NOT IN,它可以解決以下問題:
WHERE word <> ‘flibberty’ AND word <> ‘jibberty’AND word <> ‘flob’
AND word <> ‘zygotes’ AND word <> ‘zygote’ AND word <> ‘zydeco’
AND word <> NULL;
AND具有要比較的條件的結果NULL為’unknown’,因此表示式將始終返回零行。這不是錯誤;這是設計使然。您可以辯稱,NULL不應在要使用NOT IN表示式的任何列中使用a ,但是在我們的實際工作中,這些東西可能會滲入表源。值得謹慎。因此,請使用EXISTS變體或其他變體,或始終記住WHERE在IN條件中包含一個從句以消除NULLs。