2011-07-08 21 views
31

理論問題在這裏:使用IS NULL或IS NOT上連接條件NULL - 理論問題

爲什麼指定table.field爲空或table.field IS NOT NULL上的連接條件是不行的(左或右加盟例如),但只有在哪裏條件?

非工作示例:

- 這應該返回所有貨物與過濾掉任何收益(非空值)。但是,無論是否有任何內容符合[r.id is null]語句,這將返回所有貨件。

SELECT 
    * 
FROM 
    shipments s 
LEFT OUTER JOIN returns r 
    ON s.id = r.id 
    AND r.id is null 
WHERE 
    s.day >= CURDATE() - INTERVAL 10 DAY 

工作實施例:

- 該返回行正確量是總出貨量,少任何相關的回報(非空值)。

SELECT 
    * 
FROM 
    shipments s 
LEFT OUTER JOIN returns r 
    ON s.id = r.id 
WHERE 
    s.day >= CURDATE() - INTERVAL 10 DAY 
    AND r.id is null 

爲什麼會出現這種情況?兩個表之間的所有其他過濾條件都可以正常工作,但由於某些原因,除非在where語句中,否則IS NULL和IS NOT NULL過濾器不起作用。

這是什麼原因?

回答

69

例如用表A和B:

A (parent)  B (child)  
============ ============= 
id | name  pid | name 
------------ ------------- 
    1 | Alex   1 | Kate 
    2 | Bill   1 | Lia 
    3 | Cath   3 | Mary 
    4 | Dale  NULL | Pan 
    5 | Evan 

如果你想找到父母和他們的孩子,你做一個INNER JOIN

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent INNER JOIN child 
    ON parent.id  = child.pid 

結果是,一個每場比賽parentid從左邊的表和childpid從第二個表中將顯示爲結果中的一行:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
+----+--------+------+-------+ 

現在,上面沒有顯示父母沒有孩子(因爲它們的ID沒有在孩子的ID的比賽,所以你會怎麼做?你做一個外部連接。有三種類型的外連接,即左連接,右連接和全連接。我們需要左邊一個,因爲我們希望左表(父)的「額外」的行:

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 

結果是,除了以前的比賽中,沒有一場比賽所有的父母(讀:沒有孩子)也顯示:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

那裏所有那些NULL從哪裏來?那麼,MySQL(或任何其他您可能使用的RDBMS)將不知道該放什麼,因爲這些父母沒有匹配(孩子),所以沒有pidchild.name與父母匹配。所以,它把這個特殊的非價值稱爲NULL

我的意思是,這些NULLsLEFT OUTER JOIN期間創建(在結果集中)。


所以,如果我們想只顯示沒有一個孩子家長,我們可以添加一個WHERE child.pid IS NULL上述LEFT JOINJOIN完成後,對WHERE子句進行評估(檢查)。所以,這是從上面的結果清楚地表明只有最後三行在pid爲NULL會顯示:

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 

WHERE child.pid IS NULL 

結果:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

現在,如果我們繼續會發生什麼IS NULLWHERE檢查加入ON子句?

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 
    AND child.pid IS NULL 

在這種情況下,數據庫嘗試從符合這些條件的兩個表中查找行。也就是說,其中行parent.id = child.pidchild.pid IN NULL。但它可以找到沒有這樣的匹配,因爲沒有child.pid可以等於某事(1,2,3,4或5)並且同時爲NULL!

所以,條件:

ON parent.id = child.pid 
AND child.pid IS NULL 

等同於:

ON 1 = 0 

這始終是False

那麼,爲什麼它會返回左表中的所有行? 因爲這是一個左連接!和左連接返回匹配(在這種情況下沒有)左表也行不匹配檢查(所有在這種情況下行:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | NULL | NULL | 
| 2 | Bill | NULL | NULL | 
| 3 | Cath | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

希望以上解釋清楚。



阿里納斯(不直接關係到你的問題):爲什麼地球上沒有在我們的JOIN不Pan顯示出來?因爲他的pidNULL,並且SQL的(不常見)邏輯中的NULL不等於任何東西,所以它不能與任何父id(它們是1,2,3,4和5)匹配。即使在那裏有NULL,它仍然不會匹配,因爲NULL不等於任何東西,甚至不是NULL本身(這確實是一個非常奇怪的邏輯!)。這就是爲什麼我們使用特殊檢查IS NULL而不是= NULL檢查。

那麼,如果我們做了RIGHT JOIN,那麼Pan會出現嗎?是的,它會!由於右連接將顯示所有結果那場比賽(第一INNER JOIN我們所做的)加上右表中的所有行不匹配(在我們的例子是一個,在(NULL, 'Pan')行。

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent RIGHT JOIN child 
    ON parent.id  = child.pid 

結果:

+------+--------+------+-------+ 
| id | parent | pid | child | 
+---------------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| NULL | NULL | NULL | Pan | 
+------+--------+------+-------+ 

不幸的是,MySQL沒有FULL JOIN。你可以嘗試在其他RDBMS,它會顯示:

+------+--------+------+-------+ 
| id | parent | pid | child | 
+------+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
| NULL | NULL | NULL | Pan | 
+------+--------+------+-------+ 
+0

你可以通過在'LEFT JOIN'和'RIGHT JOIN'之間建立一個'NULL'來僞造一個'FULL JOIN'。這有一些限制 - 例如,你不能更新或刪除 - 可能比它的價值更麻煩。 – Duncan

6

NULL部分是在實際連接後計算的,所以這就是爲什麼它需要在where子句中。

+0

所以,如果我理解正確的話,在關係數據庫管理系統軟件忽略空計算,除非他們在WHERE子句中,但在表中加入了時間執行其他加盟條件? – JoshG

+0

@JoshG,我認爲你說得對。爲了確定列值是否爲NULL,RDMS將首先將它們結合在一起。一旦它加入它們,它將查看WHERE子句並基於此來過濾記錄。這就是爲什麼SQL專家說,考慮你的連接並查看是否有任何可以移動到JOIN條件的WHERE子句部分是明智的,因爲這樣連接將發生在更少的記錄上,並且會更快。 –

2

在處理完JOIN條件後,將評估WHERE子句。

+0

感謝您的回覆。爲什麼'IS NULL'連接條件被忽略而其他處理呢? – JoshG

+2

@JoshG:因爲NULL * NOT NULL狀態不存在,只有在*之後*求值JOIN。 –

1

你的執行計劃應該明確這一點; JOIN優先,之後結果被過濾。

+0

感謝您的回覆。所以join + all加入過濾條件是計算出來的,但加入時不是空值?任何它會忽略NULL過濾器而不是其他過濾器的原因? – JoshG

2

您正在做LEFT OUTTER JOIN這表明您希望從語句的左邊的表中的每個元組,無論它在RIGHT表中有匹配的記錄。在這種情況下,您的結果正在從RIGHT表中刪除,但結果與您在ON子句中沒有包含AND完全相同的結果。

在WHERE子句中執行AND會導致在發生LEFT JOIN之後發生修剪。

+0

感謝您的回覆。這是有道理的,只是這個邏輯似乎隻影響IS NULL AND IS NOT NULL過濾器,這很奇怪。我可以將任何其他過濾器加入連接條件,它會工作得很好。任何想法,爲什麼? – JoshG

+0

在連接期間檢查無效性;因此,您所做的只是檢查當前存在於右表中具有空值的行的行。不是以左表+右表元組結尾的後連接值(在右表中沒有匹配的情況下,使用NULL元組)。因此,通過在子句中r.id不是NULL,您只需在現有r表中查找無效值。 – Suroot

3

其實NULL過濾器不被忽略。事情是這是如何加入兩個表的工作。

我將嘗試走下數據庫服務器執行的步驟以使其理解。 例如,當您執行查詢時,您所說的是忽略NULL條件。 SELECT * FROM 發貨小號 LEFT OUTER JOIN返回r
ON s.id = r.id AND r.id爲null WHERE s.day> = CURDATE() - INTERVAL 10 DAY

發生的第一件事是表SHIPMENTS中的所有行都被選中

在下一步數據庫服務器將開始從第二個(RETURNS)表中逐一選擇記錄。

在第三步,來自RETURNS表的記錄將限制您在查詢中提供的連接條件(在本例中爲(s。id = r.id和r.id爲NULL)

請注意,應用於第三步的此限定僅決定服務器是否應接受或拒絕RETURNS表的當前記錄以附加選定的SHIPMENT表的行。它不會影響從SHIPMENT表中選擇記錄。

一旦服務器完成連接包含SHIPMENT表的所有行和RETURNS表的所選行的兩個表,它將對中間結果應用where子句。 所以當你把(r.id是NULL)條件放在where子句中時,比r.id = null的中間結果中的所有記錄都被過濾掉了。