前言
前面我們學習了NOT EXISTS和NOT IN的比較,當然少不了EXISTS和IN的比較,所以本節我們來學習EXISTS和IN的比較,簡短的內容,深入的理解,Always to review the basics。
初步探討EXISTS和IN
我們創建表Table1並且取出前面創建BigTable表中的六條數據並插入其中,同時有一條數據重復,如下:
CREATE TABLE Table1 (IntCol UNIQUEIDENTIFIER) Insert into Table1 (IntCol) Values ('b927ded5-c78b-4f53-80bf-f65a6ce86d87') Insert into Table1 (IntCol) Values ('1be326ec-4b62-4feb-8421-d9edf2df28c8') Insert into Table1 (IntCol) Values ('91c92337-24ba-4ebf-b2a3-14b987179ca6') Insert into Table1 (IntCol) Values ('c03168f8-c1c7-4903-a8ee-9b4d9c0b6b1f') Insert into Table1 (IntCol) Values ('c15ac08c-8d3d-4381-9c64-54854ddf15b7') Insert into Table1 (IntCol) Values ('c15ac08c-8d3d-4381-9c64-54854ddf15b7')
此時我們來進行IN查詢
USE TSQL2012
GO
SELECT SomeColumn
FROM BigTable
WHERE SomeColumn IN (SELECT IntCol FROM dbo.Table1)
我們在之前講過若是內部聯接中此時會返回六條數據,因為內部聯接着重強調的是JOIN后面的表,若右表有多條數據匹配上,此時則會返回多條數據,但是在IN查詢中,此時只會返回五條數據,為何如此呢?

此時用IN查詢時即使在子查詢中有重復數據時也不會擔心出問題,它會自動進行過濾處理,因為在上圖中利用了Semi Join半聯接中右半聯接或左半聯接,也就是說只返回重復的數據中的一條。那么在EXISTS中情況又是怎樣呢?
SELECT SomeColumn
FROM dbo.BigTable
WHERE EXISTS (SELECT IntCol FROM dbo.Table1)
此時因為沒有WHERE條件,此時會返回外部查詢表中所有數據,為了和上述IN查詢實現等同的結果,我們需要加上WHERE條件
USE TSQL2012
GO
SELECT SomeColumn
FROM dbo.BigTable AS bt
WHERE EXISTS (SELECT IntCol FROM dbo.Table1 AS t WHERE bt.SomeColumn = t.IntCol)
而EXISTS相對於IN來說當需要比較兩個或兩個以上條件時,EXISTS能更好的實現而IN就沒那么容易了,比如如下
SELECT SomeColumn FROM dbo.BigTable AS bt WHERE EXISTS (SELECT IntCol FROM dbo.Table1 AS t WHERE bt.SomeColumn = t.IntCol AND bt.OtherCol = t.OtherCol)
好了,到了這里我們開始講講二者性能問題
進一步探討EXISTS和IN
我們直接利用前面的表來進行查詢
SELECT ID, SomeColumn FROM BigTable
WHERE SomeColumn IN (SELECT LookupColumn FROM SmallerTable)
SELECT ID, SomeColumn FROM BigTable
WHERE EXISTS (SELECT LookupColumn FROM SmallerTable WHERE SmallerTable.LookupColumn = BigTable.SomeColumn)

二者都是利用默認的聚集索引掃描和哈希匹配中的右半聯接且開銷一致。接下來我們再來在二者查詢列上創建索引
CREATE INDEX idx_BigTable_SomeColumn
ON BigTable (SomeColumn)
CREATE INDEX idx_SmallerTable_LookupColumn
ON SmallerTable (LookupColumn)

此時只是創建了索引后查詢效率改善了,而且查詢計划較之前只是哈希匹配中的左半聯接替換成了合並聯接中的內部聯接,同時增加了流聚合。二者在開銷上仍是一致的。在我所看其他教程中印象中一直都在說利用EXISTS代替IN,其EXISTS查詢性能高於IN,而且事實卻是開銷一致,難道是100萬數據太小,還是場景不夠,還是語句不夠復雜么。都在說看使用場景,那么到底是在什么場景下EXISTS比IN性能好呢,對此有更深入了解的你們,希望在評論中得到最實際的回答。而我認為覺得用EXISTS的話,只是EXISTS比IN更加靈活而已,而且不會出現意外的結果。下面我們繼續往下看。
深入探討EXISTS和IN
我們接下來看看用IN會出現什么意外的情況,我們首先創建測試表,並插入數據如下:
USE TSQL2012 GO CREATE TABLE table1 (id INT, title VARCHAR(20), someIntCol INT) GO CREATE TABLE table12 (id INT, t1Id INT, someData VARCHAR(20)) GO
插入測試數據
INSERT INTO table1 SELECT 1, 'title 1', 5 UNION ALL SELECT 2, 'title 2', 5 UNION ALL SELECT 3, 'title 3', 5 UNION ALL SELECT 4, 'title 4', 5 UNION ALL SELECT null, 'title 5', 5 UNION ALL SELECT null, 'title 6', 5 INSERT INTO table12 SELECT 1, 1, 'data 1' UNION ALL SELECT 2, 1, 'data 2' UNION ALL SELECT 3, 2, 'data 3' UNION ALL SELECT 4, 3, 'data 4' UNION ALL SELECT 5, 3, 'data 5' UNION ALL SELECT 6, 3, 'data 6' UNION ALL SELECT 7, 4, 'data 7' UNION ALL SELECT 8, null, 'data 8' UNION ALL SELECT 9, 6, 'data 9' UNION ALL SELECT 10, 6, 'data 10' UNION ALL SELECT 11, 8, 'data 11'
table1和table2中的數據分別如下:


探討一(IN查詢導致錯誤結果)
我們來對比EXISTS和IN查詢,如下:
USE TSQL2012 GO SELECT t1.* FROM dbo.table1 AS t1 WHERE t1.id IN (SELECT t1id FROM dbo.table12) SELECT t1.* FROM dbo.table1 AS t1 WHERE exists (SELECT * FROM dbo.table12 AS t2 WHERE t1.id = t2.t1id)

此時二者返回的結果都是正確,接下來我們再來看其他情況,我們需要獲取所有table1中數據沒有在table2中的所有行。
USE TSQL2012 GO SELECT t1.* FROM dbo.table1 AS t1 WHERE NOT EXISTS (SELECT * FROM dbo.table12 AS t2 WHERE t1.id = t2.t1id) SELECT t1.* FROM dbo.table1 as t1 WHERE t1.id NOT IN (SELECT t1id FROM dbo.table12 as t2)

此時利用EXISTS得到了正確的結果,而通過IN查詢未達到我們查詢的目的,原因之前也有說過IN是基於三值邏輯,此時遇到NULL則會當做UNKNOWN來處理,所以最終得到的結果集是錯誤的。我們繼續往下探討。
探討2(手寫錯誤導致意外結果)
我們重新創建測試表並插入測試數據,如下:
USE TSQL2012 GO CREATE TABLE TestTable1 (id1 int) CREATE TABLE TestTable2 (id2 int) INSERT TestTable1 VALUES(1),(2),(3) INSERT TestTable2 VALUES(1),(2)
我們首先進行如下查詢:
USE TSQL2012 GO SELECT * FROM TestTable1 WHERE id1 IN (SELECT id2 FROM TestTable2)

此時結果是正確的,假如在子查詢中我們將列id2寫成了id1,那么情況又會是怎樣的呢?
SELECT *
FROM TestTable1
WHERE id1 IN (SELECT id1 FROM TestTable2)

不知你是否注意到什么沒有,表面是沒什么問題,我們接着運行下上述子查詢
SELECT id1 FROM TestTable2

單獨運行查詢時,結果居然出錯了,到這了我們再看下創建的表的列,id1是在Table1中而非在Table2中,所以導致了這種意外的錯誤,如果手寫錯誤,結果數據也有,一般是不會覺察不到,通過使用IN查詢就導致了意外的出現。而如下利用EXISTS時會直接報錯,而不是得到錯誤的結果集
SELECT *
FROM t1
WHERE EXISTS (SELECT * FROM TestTable2 t2 WHERE t2.id2 = t1.id1 )
當然了也有人會說根本不會犯這樣低級錯誤,但是誰能保證呢,SQL有智能提示更加容易犯這樣的錯誤,因為直接在子查詢就會有這樣的列出現,但是該列在子查詢表中根本不存在。所以基於探討的兩點,利用EXISTS更加保險。到此,關於EXISTS和IN的介紹算是結束,下此結論。
EXISTS和IN性能分析結論:我們推薦使用EXISTS,而不是IN,原因不是EXISTS性能優於IN,二者性能開銷是一樣的,而是利用EXISTS比IN更加靈活,更加安全、保險不會出現意想不到的結果。
總結
本節我們講解了EXISTS和IN,關於其二者在性能方面還是有點疑惑,畢竟場景不夠,當然最后還是推薦使用EXISTS,而原因不在於性能。我們下節講解LEFT JOIN和NOT EXISTS,簡短的內容,深入的理解,我們下節再會。
