MySQL:left join 避坑指南

2021-02-19 java那些事

現象

left join在我們使用mysql查詢的過程中可謂非常常見,比如博客裡一篇文章有多少條評論、商城裡一個貨物有多少評論、一條評論有多少個贊等等。

但是由於對join、on、where等關鍵字的不熟悉,有時候會導致查詢結果與預期不符,所以今天我就來總結一下,一起避坑。

這裡我先給出一個場景,並拋出兩個問題,如果你都能答對那這篇文章就不用看了。

假設有一個班級管理應用,有一個表classes,存了所有的班級;有一個表students,存了所有的學生,具體數據如下(感謝廖雪峰的在線SQL):

SELECT * FROM classes;

SELECT * FROM students;

那麼現在有兩個需求:

找出每個班級的名稱及其對應的女同學數量

找出一班的同學總數

對於需求1,大多數人不假思索就能想出如下兩種sql寫法,請問哪種是對的?

SELECT c.name, count(s.name) as num 
    FROM classes c left join students s 
    on s.class_id = c.id 
    and s.gender = 'F'
    group by c.name

或者

SELECT c.name, count(s.name) as num 
    FROM classes c left join students s 
    on s.class_id = c.id 
    where s.gender = 'F'
    group by c.name

對於需求2,大多數人也可以不假思索的想出如下兩種sql寫法,請問哪種是對的?

SELECT c.name, count(s.name) as num 
    FROM classes c left join students s 
    on s.class_id = c.id 
    where c.name = '一班' 
    group by c.name

或者

SELECT c.name, count(s.name) as num 
    FROM classes c left join students s 
    on s.class_id = c.id 
    and c.name = '一班' 
    group by c.name

請不要繼續往下翻 !!先給出你自己的答案,正確答案就在下面。
~
~
~
~
~
~
~
~
~
~
~
~
~
~
~
~

答案是兩個需求都是第一條語句是正確的,要搞清楚這個問題,就得明白mysql對於left join的執行原理,下節進行展開。

根源

mysql 對於left join的採用類似嵌套循環的方式來進行從處理,以下面的語句為例:

SELECT * FROM LT LEFT JOIN RT ON P1(LT,RT)) WHERE P2(LT,RT)

其中P1是on過濾條件,缺失則認為是TRUE,P2是where過濾條件,缺失也認為是TRUE,該語句的執行邏輯可以描述為:

FOR each row lt in LT {// 遍歷左表的每一行
  BOOL b = FALSE;
  FOR each row rt in RT such that P1(lt, rt) {// 遍歷右表每一行,找到滿足join條件的行
    IF P2(lt, rt) {//滿足 where 過濾條件
      t:=lt||rt;//合併行,輸出該行
    }
    b=TRUE;// lt在RT中有對應的行
  }
  IF (!b) { // 遍歷完RT,發現lt在RT中沒有有對應的行,則嘗試用null補一行
    IF P2(lt,NULL) {// 補上null後滿足 where 過濾條件
      t:=lt||NULL; // 輸出lt和null補上的行
    }         
  }
}

當然,實際情況中MySQL會使用buffer的方式進行優化,減少行比較次數,不過這不影響關鍵的執行流程,不在本文討論範圍之內。

從這個偽代碼中,我們可以看出兩點:

如果想對右表進行限制,則一定要在on條件中進行,若在where中進行則可能導致數據缺失,導致左表在右表中無匹配行的行在最終結果中不出現,違背了我們對left join的理解。

因為對左表無右表匹配行的行而言,遍歷右表後b=FALSE,所以會嘗試用NULL補齊右表,但是此時我們的P2對右表行進行了限制,NULL若不滿足P2(NULL一般都不會滿足限制條件,除非IS NULL這種),則不會加入最終的結果中,導致結果缺失。

如果沒有where條件,無論on條件對左表進行怎樣的限制,左表的每一行都至少會有一行的合成結果,對左表行而言,若右表若沒有對應的行,則右表遍歷結束後b=FALSE,會用一行NULL來生成數據,而這個數據是多餘的。所以對左表進行過濾必須用where。

下面展開兩個需求的錯誤語句的執行結果和錯誤原因:

需求1

需求2

需求1由於在where條件中對右表限制,導致數據缺失(四班應該有個為0的結果)

需求2由於在on條件中對左表限制,導致數據多餘(其他班的結果也出來了,還是錯的)

總結

通過上面的問題現象和分析,可以得出了結論:在left join語句中,左表過濾必須放where條件中,右表過濾必須放on條件中,這樣結果才能不多不少,剛剛好。

SQL 看似簡單,其實也有很多細節原理在裡面,一個小小的混淆就會造成結果與預期不符,所以平時要注意這些細節原理,避免關鍵時候出錯。

作者:MageekChiu

segmentfault.com/a/1190000020458807

推薦程式設計師必備微信號 

在這裡,我們分享程式設計師相關技術,職場生活,行業熱點資訊。不定期還會分享IT趣文和趣圖。這裡屬於我們程式設計師自己的生活,工作和娛樂空間。

相關焦點

  • MySQL:Left Join 避坑指南
    join在我們使用mysql查詢的過程中可謂非常常見,比如博客裡一篇文章有多少條評論、商城裡一個貨物有多少評論、一條評論有多少個贊等等。答案是兩個需求都是第一條語句是正確的,要搞清楚這個問題,就得明白mysql對於left join的執行原理,下節進行展開。
  • MySQL:LEFT JOIN 避坑指南
    join在我們使用mysql查詢的過程中可謂非常常見,比如博客裡一篇文章有多少條評論、商城裡一個貨物有多少評論、一條評論有多少個贊等等。, count(s.name) as num     FROM classes c left join students s     on s.class_id = c.id     and c.name = '一班'     group by c.name請不要繼續往下翻 !!
  • MySql 之 left join 避坑指南
    join在我們使用mysql查詢的過程中可謂非常常見,比如博客裡一篇文章有多少條評論、商城裡一個貨物有多少評論、一條評論有多少個贊等等。, count(s.name) as num FROM classes c left join students s on s.class_id = c.id and c.name = '一班' group by c.name請不要繼續往下翻 !!
  • 關於Left join,避坑指南
    join 是 SQL查詢中很常見的一種操作,具體來講有join,left join, right join,full join等很多形式。具體的原理如下圖所示。但其中最常見的還是使用left join 。
  • MySQL中left join的幾個SQL對比
    mysql> select t1.id,t1.name from test1 t1 left join test2 t2 on t1.id=t2.id and t1.name='bb'; +----+-+| id | name |+----+-+| 1 | aa || 2 | bb || 3 | cc || 4 |
  • 場景分析:記錄一下使用MySQL的left join時,遇到的坑!
    join在我們使用mysql查詢的過程中可謂非常常見,比如博客裡一篇文章有多少條評論、商城裡一個貨物有多少評論、一條評論有多少個贊等等。c.name, count(s.name) as numFROM classes c left join students son s.class_id = c.idand c.name = '一班'group by c.name請不要繼續往下翻 !!
  • 超詳細mysql left join,right join,inner join用法分析
    比較詳細的mysql的幾種連接功能分析下面是例子分析表A記錄如下: aID        aNum
  • mysql 如何優化left join
    join優化的問題,搞了一下午,中間查了不少資料,對MySQL的查詢計劃還有查詢優化有了更進一步的了解,做一個簡單的記錄: select c.* from hotel_info_original c left join hotel_info_collection h on c.hotel_type=h.hotel_type
  • 關於Left join,你可能不知道這些......
    join 是 SQL查詢中很常見的一種操作,具體來講有join,left join, right join,full join等很多形式。具體的原理如下圖所示。但其中最常見的還是使用left join 。
  • mysql之left join 詳解
    讓我們看一個 LFET JOIN 示例:mysql> CREATE TABLE `product` (  `id` int(10) unsigned NOT NULL auto_increment,  `amount` int(10) unsigned default NULL,  PRIMARY KEY  (`id`)
  • inner join 與 left join 之間的區別
    關於inner join 與 left join 之間的區別,以前以為自己搞懂了,今天從前端取參數的時候發現不是預想中的結果
  • left semi join 與 inner join 相同點與區別
    今天無意中發現了left semi join這種連接關鍵字的用法,在這裡分享一下,避免大家以後踩坑。1.
  • 我想說:mysql的join 真的很弱
    此時說明mysql查詢有些吃力了,但是仍然嫩查詢出來。 步驟5.1,mysql查詢不出來,4表連接,對我本機mysql來說,1.5億數據超過極限了(我調優過這個SQL,執行計劃和索引都走了,沒有問題,show profile顯示在sending data.這個問題另外文章詳談。)
  • 「看這篇就夠了」Mysql join條件是要寫在on裡還是在where裡?
    對於join系列語句,大部分開發人員都經常用到。但是對於裡面的運行原理,我相信很少人真正認識,下面我們從幾個方面介紹下。為了能夠覆蓋更多的點,這裡複製一位大佬的表和圖。算法區別select * from a left join b on(a.f1=b.f1) and (a.f2=b.f2)語句執行順序是:1、先掃描a表的數據,放到join_buffer中,join_buffer的數據結構是數組。
  • SQL-JOIN全解析
    例如下面這張用爛了的圖,可以幫你快速理解每個join用法的效果:這張圖描述了left join(左連接)、right join(右連接) 、inner join(內連接)、outer join(外連接)相關的7種用法。
  • 自閉症康復機構選擇的避「坑」指南
    避坑指南 家長在選擇時一定要對機構的基本開辦資質做一個了解,比如,可以直接詢問機構是否有營業執照?經營範圍中是否包含與自閉症康復相關聯的內容?註冊資金有多少?避坑指南 家長在向機構諮詢了解可能的訓練結果時,如果機構人員承諾「包治」「一定痊癒」「可以完全恢復正常」,那麼,這樣的機構要考慮規避。
  • 【面試必備】SQL中left join、right join、inner join的區別
    joinsql語句如下:select * from Aleft join B on A.aID = B.bID結果如下:aID     aNum     bID     bName1     a20050111    1     20060324012     a20050112    2
  • SQL表連接查詢(inner join、full join、left join、right join)
    一、內連接-inner jion :最常見的連接查詢可能是這樣,查出學生的名字和成績:select s.name,m.mark from student s,mark m where s.id=m.studentid上面就是我們最常見的inner join
  • Mysql - JOIN詳解
    如果之前對不同JOIN的執行結果沒有概念,可以結合這篇文章往下看2 JOIN的執行順序以下是JOIN查詢的通用結構SELECT <row_list> FROM <left_table> <inner|left|right> JOIN <right_table> ON
  • MySQL 8.0 新特性:哈希連接(Hash Join)
    https://dev.mysql.com/doc/refman/8.0/en/hash-joins.htmlMySQL 實現了用於內連接查詢的 hash join 方式。 buffer (Block Nested Loop)Hash join 連接同樣適用於不指定查詢條件時的笛卡爾積(Cartesian product),例如:mysql> EXPLAIN FORMAT=TREE    -> SELECT *    ->     FROM t1