left join 連表問題解析:on後多條件無效 & where與on的區別

在項目中用到多表聯合查詢,發現2個現象,今天解決這2個疑問:

1、left join連接2張表,on後的條件第一個生效,用and連接的其他條件不生效;

2、一旦加上where,則顯示的結果等同於inner join;

先寫結論:

過濾條件放在:

where後面:是先連接然生成臨時查詢結果,然後再篩選

on後面:先根據條件過濾篩選,再連 生成臨時查詢結果

table1 left join table2 on 條件1(有效)  and 條件2(無效)

table1 left join table2 on 條件1(有效)  where 條件2(有效)

table1 left join table2 on (條件1(有效)  and 條件2(有效))


1、left join連接2張表,on後的條件第一個生效,用and連接的其他條件不生效;

舉例:

  • ON 子句與 WHERE 子句的不同

“A LEFT JOIN B ON 條件表達式”:

ON 條件(“A LEFT JOIN B ON 條件表達式”中的ON)用來決定如何從 B 表中檢索數據行。

如果 B 表中沒有任何一行數據匹配 ON 的條件,將會額外生成一行所有列爲 NULL 的數據

在匹配階段 WHERE 子句的條件都不會被使用。僅在匹配階段完成以後,WHERE 子句條件纔會被使用。它將從匹配階段產生的數據中檢索過濾。

讓我們看一個 LFET JOIN 示例:

mysql> CREATE TABLE `product` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `amount` int(10) unsigned default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=latin1
 
mysql> CREATE TABLE `product_details` (
  `id` int(10) unsigned NOT NULL,
  `weight` int(10) unsigned default NULL,
  `exist` int(10) unsigned default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
 
mysql> INSERT INTO product (id,amount)
       VALUES (1,100),(2,200),(3,300),(4,400);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0
 
mysql> INSERT INTO product_details (id,weight,exist)
       VALUES (2,22,0),(4,44,1),(5,55,0),(6,66,1);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0
 
mysql> SELECT * FROM product;
+----+--------+
| id | amount |
+----+--------+
|  1 |    100 |
|  2 |    200 |
|  3 |    300 |
|  4 |    400 |
+----+--------+
4 rows in set (0.00 sec)
 
mysql> SELECT * FROM product_details;
+----+--------+-------+
| id | weight | exist |
+----+--------+-------+
|  2 |     22 |     0 |
|  4 |     44 |     1 |
|  5 |     55 |     0 |
|  6 |     66 |     1 |
+----+--------+-------+
4 rows in set (0.00 sec)
 
mysql> SELECT * FROM product LEFT JOIN product_details
       ON (product.id = product_details.id);
+----+--------+------+--------+-------+
| id | amount | id   | weight | exist |
+----+--------+------+--------+-------+
|  1 |    100 | NULL |   NULL |  NULL |
|  2 |    200 |    2 |     22 |     0 |
|  3 |    300 | NULL |   NULL |  NULL |
|  4 |    400 |    4 |     44 |     1 |
+----+--------+------+--------+-------+
4 rows in set (0.00 sec)

ON 子句和 WHERE 子句有什麼不同?舉例說明:

mysql> SELECT * FROM product LEFT JOIN product_details
       ON (product.id = product_details.id)
       AND product_details.id=2;
+----+--------+------+--------+-------+
| id | amount | id   | weight | exist |
+----+--------+------+--------+-------+
|  1 |    100 | NULL |   NULL |  NULL |
|  2 |    200 |    2 |     22 |     0 |
|  3 |    300 | NULL |   NULL |  NULL |
|  4 |    400 | NULL |   NULL |  NULL |
+----+--------+------+--------+-------+
4 rows in set (0.00 sec)
 
mysql> SELECT * FROM product LEFT JOIN product_details
       ON (product.id = product_details.id)
       WHERE product_details.id=2;
+----+--------+----+--------+-------+
| id | amount | id | weight | exist |
+----+--------+----+--------+-------+
|  2 |    200 |  2 |     22 |     0 |
+----+--------+----+--------+-------+
1 row in set (0.01 sec)

第一條查詢使用 ON 條件決定了從 LEFT JOIN的 product_details表中檢索符合的所有數據行。

第二條查詢做了簡單的LEFT JOIN,然後使用 WHERE 子句從 LEFT JOIN的數據中過濾掉不符合條件的數據行。

再來看一些示例:

mysql>
mysql> SELECT * FROM product LEFT JOIN product_details
       ON product.id = product_details.id
       AND product.amount=100;
+----+--------+------+--------+-------+
| id | amount | id   | weight | exist |
+----+--------+------+--------+-------+
|  1 |    100 | NULL |   NULL |  NULL |
|  2 |    200 | NULL |   NULL |  NULL |
|  3 |    300 | NULL |   NULL |  NULL |
|  4 |    400 | NULL |   NULL |  NULL |
+----+--------+------+--------+-------+
4 rows in set (0.00 sec)

所有來自product表的數據行都被檢索到了,但沒有在product_details表中匹配到記錄(product.id = product_details.id AND product.amount=100 條件並沒有匹配到任何數據)

mysql> SELECT * FROM product LEFT JOIN product_details
       ON (product.id = product_details.id)
       AND product.amount=200;
+----+--------+------+--------+-------+
| id | amount | id   | weight | exist |
+----+--------+------+--------+-------+
|  1 |    100 | NULL |   NULL |  NULL |
|  2 |    200 |    2 |     22 |     0 |
|  3 |    300 | NULL |   NULL |  NULL |
|  4 |    400 | NULL |   NULL |  NULL |
+----+--------+------+--------+-------+
4 rows in set (0.01 sec)

同樣,所有來自product表的數據行都被檢索到了,有一條數據匹配到了。

使用 WHERE ... IS NULL 子句的 LEFT JOIN

當你使用 WHERE ... IS NULL 子句時會發生什麼呢?

如前所述,WHERE 條件查詢發生在 匹配階段之後,這意味着 WHERE ... IS NULL 子句將從匹配階段後的數據中過濾掉不滿足匹配條件的數據行。

紙面上看起來很清楚,但是當你在 ON 子句中使用多個條件時就會感到困惑了。

我總結了一種簡單的方式來理解上述情況:

  • 將 IS NULL 作爲否定匹配條件
  • 使用 !(A and B) == !A OR !B 邏輯判斷

看看下面的示例:

mysql> SELECT a.* FROM product a LEFT JOIN product_details b
       ON a.id=b.id AND b.weight!=44 AND b.exist=0
       WHERE b.id IS NULL;
+----+--------+
| id | amount |
+----+--------+
|  1 |    100 |
|  3 |    300 |
|  4 |    400 |
+----+--------+
3 rows in set (0.00 sec)

讓我們檢查一下 ON 匹配子句:

(a.id=b.id) AND (b.weight!=44) AND (b.exist=0)

我們可以把 IS NULL 子句 看作是否定匹配條件。

這意味着我們將檢索到以下行:

!( exist(b.id that equals to a.id) AND b.weight !=44 AND b.exist=0 )
!exist(b.id that equals to a.id) || !(b.weight !=44) || !(b.exist=0)
!exist(b.id that equals to a.id) || b.weight =44 || b.exist=1

就像在C語言中的邏輯 AND 和 邏輯 OR表達式一樣,其操作數是從左到右求值的。如果第一個參數做夠判斷操作結果,那麼第二個參數便不會被計算求值(短路效果)

看看別的示例:

mysql> SELECT a.* FROM product a LEFT JOIN product_details b
       ON a.id=b.id AND b.weight!=44 AND b.exist=1
       WHERE b.id IS NULL;
+----+--------+
| id | amount |
+----+--------+
|  1 |    100 |
|  2 |    200 |
|  3 |    300 |
|  4 |    400 |
+----+--------+
4 rows in set (0.00 sec)

Matching-Conditions 與 Where-conditions 之戰

如果你把基本的查詢條件放在 ON 子句中,把剩下的否定條件放在 WHERE 子句中,那麼你會獲得相同的結果。

例如,你可以不這樣寫:

SELECT a.* FROM product a LEFT JOIN product_details b
ON a.id=b.id AND b.weight!=44 AND b.exist=0
WHERE b.id IS NULL;

你可以這樣寫:

SELECT a.* FROM product a LEFT JOIN product_details b
ON a.id=b.id
WHERE b.id is null OR b.weight=44 OR b.exist=1;

 

mysql> SELECT a.* FROM product a LEFT JOIN product_details b
       ON a.id=b.id
       WHERE b.id is null OR b.weight=44 OR b.exist=1;
+----+--------+
| id | amount |
+----+--------+
|  1 |    100 |
|  3 |    300 |
|  4 |    400 |
+----+--------+
3 rows in set (0.00 sec)

你可以不這樣寫:

SELECT a.* FROM product a LEFT JOIN product_details b
ON a.id=b.id AND b.weight!=44 AND b.exist!=0
WHERE b.id IS NULL;

可以這樣寫:

SELECT a.* FROM product a LEFT JOIN product_details b
ON a.id=b.id
WHERE b.id is null OR b.weight=44 OR b.exist=0;

 

mysql> SELECT a.* FROM product a LEFT JOIN product_details b
       ON a.id=b.id
       WHERE b.id is null OR b.weight=44 OR b.exist=0;
+----+--------+
| id | amount |
+----+--------+
|  1 |    100 |
|  2 |    200 |
|  3 |    300 |
|  4 |    400 |
+----+--------+
4 rows in set (0.00 sec)

這些查詢真的效果一樣?

如果你只需要第一個表中的數據的話,這些查詢會返回相同的結果集。有一種情況就是,如果你從 LEFT JOIN的表中檢索數據時,查詢的結果就不同了。

如前所屬,WHERE 子句是在匹配階段之後用來過濾的。

例如:

mysql> SELECT * FROM product a LEFT JOIN product_details b
       ON a.id=b.id AND b.weight!=44 AND b.exist=1
       WHERE b.id is null;
+----+--------+------+--------+-------+
| id | amount | id   | weight | exist |
+----+--------+------+--------+-------+
|  1 |    100 | NULL |   NULL |  NULL |
|  2 |    200 | NULL |   NULL |  NULL |
|  3 |    300 | NULL |   NULL |  NULL |
|  4 |    400 | NULL |   NULL |  NULL |
+----+--------+------+--------+-------+
4 rows in set (0.00 sec)
 
mysql> SELECT * FROM product a LEFT JOIN product_details b
       ON a.id=b.id
       WHERE b.id IS NULL OR b.weight=44 OR b.exist=0;
+----+--------+------+--------+-------+
| id | amount | id   | weight | exist |
+----+--------+------+--------+-------+
|  1 |    100 | NULL |   NULL |  NULL |
|  2 |    200 |    2 |     22 |     0 |
|  3 |    300 | NULL |   NULL |  NULL |
|  4 |    400 |    4 |     44 |     1 |
+----+--------+------+--------+-------+
4 rows in set (0.00 sec)

總附註:

如果你使用 LEFT JOIN 來尋找在一些表中不存在的記錄,你需要做下面的測試:WHERE 部分的 col_name IS NULL(其中 col_name 列被定義爲 NOT NULL),MYSQL 在查詢到一條匹配 LEFT JOIN 條件後將停止搜索更多行(在一個特定的組合鍵下)。

2、一旦加上where,則顯示的結果等同於inner join;

舉例:

select a.*,b.*
from table1 a
left join table2 b on b.X=a.X
where XXX

如上:一旦使用了left join,沒有where條件時,左表table1會顯示全部內容

           使用了where,只有滿足where條件的記錄纔會顯示(左表顯示部分或者全部不顯示)

 

原因分析:

數據庫在通過連接兩張或多張表來返回記錄時,都會生成一張中間的臨時表,然後再將這張臨時表返回給用戶;

數據庫在通過連接兩張或多張表來返回記錄時,都會生成一張中間的臨時表,然後再將這張臨時表返回給用戶。

在使用left jion時,on和where條件的區別如下:

1、用where 是先連接然後再篩選   
2、用and 是先篩選再連接

3、on條件是在生成臨時表時使用的條件,它不管on中的條件是否爲真,都會返回左邊表中的記錄。

4、where條件是在臨時表生成好後,再對臨時表進行過濾的條件。這時已經沒有left join的含義(必須返回左邊表的記錄)了,條件不爲真的就全部過濾掉。

假設有兩張表:

表1 tab1:

id size

1 10

2 20

3 30

表2 tab2:

size name

10 AAA

20 BBB

20 CCC


兩條SQL:
1、select * form tab1 left join tab2 on (tab1.size = tab2.size) where tab2.name=’AAA’
2、select * form tab1 left join tab2 on (tab1.size = tab2.size and tab2.name=’AAA’)

第一條SQL的過程:

1、中間表
on條件: 
tab1.size = tab2.size

tab1.id    tab1.size    tab2.size     tab2.name

1               10                   10               AAA

2              20                     20             BBB

2             20                      20               CCC

3             30                    (null)              (null)

2、再對中間表過濾
where 條件:
tab2.name=’AAA’

tab1.id       tab1.size        tab2.size     tab2.name

1                  10                  10              AAA


第二條SQL的過程:

1、中間表
on條件: 
tab1.size = tab2.size and tab2.name=’AAA’
(條件不爲真也會返回左表中的記錄)

tab1.id      tab1.size         tab2.size       tab2.name

1               10                     10                   AAA

2               20                   (null)               (null)

3               30                    (null)                 (null)

     其實以上結果的關鍵原因就是left join,right join,full join的特殊性,不管on上的條件是否爲真都會返回left或right表中的記錄,full則具有left和right的特性的並集。 而inner jion沒這個特殊性,則條件放在on中和where中,返回的結果集是相同的。

 

解決方案:

1、where過濾結果作爲子查詢,和主表left,如下:

select a.*,tmp.*
from table1 a
left join(
    select a.*,b.*
    from table1 a
    left join table2 b on b.X=a.X
    where XXX
)tmp

 

很明顯,子查詢語句無論 left join、inner join都沒啥區別了

2、查詢條件放在on後面

select a.*,b.*

from table1 a

left join table2 b on b.X=a.X and XXX

分析:注意:where XXX去掉,改爲鏈接條件on後面的 and XXX

 on條件是在生成臨時表時使用的條件,它不管on中的條件是否爲真,都會返回左邊表中的記錄。

 

 

https://www.cnblogs.com/huahua035/p/5718469.html

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章