首先我要感謝aa和Liu Xing幫我發(fā)現(xiàn)了我日志中的錯(cuò)誤。之前比較粗心,把3條SQL語句寫成一樣的了,對于給讀者造成的麻煩,我深表抱歉。
今天我把原文做了修訂,為了對得起讀者對我的關(guān)注,我重新深入的研究了這個(gè)問題,在后面,我會(huì)把來龍去脈寫清楚。
問題:
語句1:
Select * from table1 A where A.col1 not in ( select col1 from table2 B )
如果這樣,本來應(yīng)該有一條數(shù)據(jù),結(jié)果沒有。
如果我改寫成這樣:
語句2:
select * from table1 A where not exists (SELECT * FROM table2 B where B.col1 = A.col1)
結(jié)果就正確,有一條數(shù)據(jù)顯示。
經(jīng)過一番搜索,原以為是子查詢結(jié)果集太大的原因。
后來有網(wǎng)上強(qiáng)人指點(diǎn):子查詢里面有空集。即子查詢的結(jié)果集里面有NULL的結(jié)果。
把查詢語句修改成:
語句3:
Select * from table1 A where A.col1 not in ( select col1 from table2 B where B.col1 is not null )
果然就查出來了。而且一點(diǎn)不差。。。厲害阿~~~
下面是針對本文題的分析:
1。 首先來說說Oracle中的NULL。
Oracle中的NULL代表的是無意義,或者沒有值。將NULL和其他的值進(jìn)行邏輯運(yùn)算,運(yùn)算過程中,NULL的表現(xiàn)更象是FALSE。
下面請看真值表:
|
AND NULL
|
OR NULL |
TRUE |
NULL |
TRUE |
FALSE |
FALSE |
NULL |
NULL |
NULL
|
NULL |
另外,NULL和其他的值進(jìn)行比較或者算術(shù)運(yùn)算(<、>、=、!=、+、-、*、/),結(jié)果仍是NULL。
如果想要判定某個(gè)值是否為NULL,可以用IS NULL或者IS NOT NULL。
2. 再來說說Oracle中的IN。
in是一個(gè)成員條件, 對于給定的一個(gè)集合或者子查詢,它會(huì)比較每一個(gè)成員值。
IN功能上相當(dāng)于 =ANY 的操作,而NOT IN 功能上相當(dāng)于 !=ALL 的操作。
IN在邏輯上實(shí)際上就是對給定的成員集合或者子查詢結(jié)果集進(jìn)行逐條的判定,例如:
SELECT * FROM table1 A WHERE A.col1 in (20,50,NULL);
實(shí)際上就是執(zhí)行了
SELECT * FROM table1 A WHERE A.col1=20 OR A.col1=50 OR A.col1=NULL;
這樣,根據(jù)NULL的運(yùn)算特點(diǎn)和真值表,我們可以看出,上邊這個(gè)WHERE 字句可以被簡化(如果返回NULL則無結(jié)果集返回,這一點(diǎn)和FALSE是一樣的)為
WHERE A.col1=20 OR A.col1=50
也就是說,如果你的table1中真的存在含有NULL值的col1列,則執(zhí)行該語句,無法查詢出那些值為null的記錄。
再來看看NOT IN。根據(jù)邏輯運(yùn)算關(guān)系,我們知道,NOT (X=Y OR N=M) 等價(jià)于 X!=Y AND N!=M,那么:
SELECT * FROM table1 A WHERE A.col1 not in (20,50,NULL)
等價(jià)于
SELECT * FROM table1 A WHERE A.col1!=20 AND A.col1!=50 AND A.col1!=NULL
根據(jù)NULL的運(yùn)算特性和真值表,該語句無論前兩個(gè)判定條件是否為真,其結(jié)果一定是NULL或者FALSE。故絕對沒有任何記錄可以返回。
這就是為什么
語句1查不到應(yīng)有結(jié)果的原因。當(dāng)然,如果你用NOT IN的時(shí)候,預(yù)先在子查詢里把NULL去掉的話,那就沒問題了,例如
語句3。
有些童鞋可能要問了:那如果我想把A表里面那些和B表一樣col1列的值一樣的記錄都查出來,即便A、B兩表里面的col1列都包括值為NULL的記錄的話,用這一條語句就沒辦法了嗎?
我只能很遺憾的告訴你,如果你想在WHERE后面單純用IN 似乎不太可能了,當(dāng)然,你可以在外部的查詢語句中將NULL條件并列進(jìn)去,例如:
SELECT * FROM table1 A WHERE A.col1 in (SELECT B.col1 FROM table2 B) OR A.col1 IS NULL;
3. 最后談?wù)凟XISTS。
有人說EXISTS的性能比IN要好。但這是很片面的。我們來看看EXISTS的執(zhí)行過程:
select * from t1 where exists ( select * from t2 where t2.col1 = t1.col1 )
相當(dāng)于:
for x in ( select * from t1 )
loop
if ( exists ( select * from t2 where t2.col1 = x.col1 )
then
OUTPUT THE RECORD in x
end if
end loop
也就是說,EXISTS語句實(shí)際上是通過循環(huán)外部查詢的結(jié)果集,來過濾出符合子查詢標(biāo)準(zhǔn)的結(jié)果集。于是外部查詢的結(jié)果集數(shù)量對該語句執(zhí)行性能影響最大,故如果外部查詢的結(jié)果集數(shù)量龐大,用EXISTS語句的性能也不一定就會(huì)好很多。
當(dāng)然,有人說NOT IN是對外部查詢和子查詢都做了全表掃描,如果有索引的話,還用不上索引,但是NOT EXISTS是做連接查詢,所以,如果連接查詢的兩列都做了索引,性能會(huì)有一定的提升。
當(dāng)然至于實(shí)際的查詢效率,我想還是具體情況具體分析吧。
那么我們不妨來分析一下語句2為什么能夠的到正確的結(jié)果吧:
語句2是這樣的:
select * from table1 A where not exists (SELECT B.col1 FROM table2 B where B.col1 = A.col1)
實(shí)際上是這樣的執(zhí)行過程:
for x in ( select * from table1 A )
loop
if (not exists ( select * from table2 B where B.col1 = x.col1 )
then
OUTPUT THE RECORD in x
end if
end loop
由于表A中不包含NULL的記錄,所以,遍歷完表A,也只能挑出表A中獨(dú)有的記錄。
這就是為什么
語句2能夠完成
語句3的任務(wù)的原因。
但如果表A中存在NULL記錄而表B中不存在呢?
這個(gè)問題請大家自己分析吧。哈哈。有答案了可以給我留言哦。
答案:A表中的NULL也會(huì)被查出來。因?yàn)閟elect * from table2 B where B.col1 = NULL不返回結(jié)果,故
not exists ( select * from table2 B where B.col1 = x.col1 )的值為真。
以上SQL運(yùn)行結(jié)果在MySQL和Oracle上都已經(jīng)通過。