mysql-left join的坑和優化經驗


參考文章:https://blog.csdn.net/weixin_39980841/article/details/110807850

CREATE TABLE classes (
`id` INT(11) NOT NULL PRIMARY KEY,
`name` VARCHAR(32) NOT NULL
)

INSERT INTO classes (`id`, `name`) VALUES
(1, '一班'),
(2, '二班'),
(3, '三班'),
(4, '四班')


CREATE TABLE students (
`id` INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`class_id` INT(11) NOT NULL,
`name` VARCHAR(32),
`gender` VARCHAR(1)
)

INSERT INTO students(
`class_id`,
`name`,
`gender`
)
VALUES
(1, '小明', 'M'),
(1, '小紅', 'F'),
(1, '小軍', 'M'),
(1, '小米', 'F'),
(2, '小白', 'M'),
(2, '小兵', 'F'),
(2, '小林', 'F'),
(3, '小新', 'F'),
(3, '小王', 'M'),
(3, '小麗', 'F')

 

根源

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

 

99abc84ead2add47b492e3f60afc131e.png

需求2

 

971d4793a157739ce6ce3c93767b72b0.png

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

正確的slq應該為

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

  

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

總結

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

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

========================

根據經驗。我更喜歡直接用where組合成為虛擬表,這樣虛擬表能直接使用到索引,也能較少笛卡爾乘積,使得大表的查詢效率大大提高,也絕不會發生錯誤。

優化后的sql為

SELECT c.name, COUNT(s.name) AS num 
    FROM classes c LEFT JOIN (SELECT * FROM students s WHERE s.gender = 'F') AS s
    ON s.class_id = c.id
    GROUP BY c.name

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM