3.8 解決方案隨筆
本節內容相當雜;介紹了怎樣編寫解決各種問題的查詢。多數內容是在郵件清單上看到的解決問題的方案(謝謝清單上的那些朋友,他們為解決方案作了很多工作)。
3.8.1將子選擇編寫為連接
MySQL自3.24版本以來才具有子選擇功能。這項功能的缺少是MySQL中一件常常令人惋惜的事,但有一件事很多人似乎沒有認識到,那就是用子選擇編寫的查詢通常可以用連接來編寫。實事上,即使MySQL具有了子查詢,檢查用子選擇編寫的查詢也是一件苦差事;用連接而不是用子選擇來編寫會更為有效。
1. 重新編寫選擇匹配值的子選擇
下面是一個包含一個子選擇查詢的樣例,它從score 表中選擇所有測試的學分(即,忽略測驗的學分):
可通過將其轉換為一個簡單的連接,不用子選擇也可以編寫出相同的查詢,如下所示:
下面的例子為選擇女學生的學分:
可將其轉換為連接,如下所示:
這裡是一個模式,子選擇查詢如下形式:
這樣的查詢可轉換為如下形式的連接:
2. 重新編寫選擇非匹配值的子選擇查詢
另一種常用的子選擇查詢是查找一個表中有的而另一個表中沒有的值。正如以前所看到的那樣,“那些未給出的值”這一類的問題是LEFT JOIN 可能有用的一個線索。下面的查詢包含一個子選擇(它尋找那些全勤的學生):
3.8.2 檢查表中未給出的值
我們已經在3 . 6節“檢索記錄”中看到,在要想知道一個表中哪些值不出現在另一表中時,可對兩個表使用LEFT JOIN 並查找那些從第二個表中選中NULL 的行。並用下列兩個表舉例:
現在讓我們來考慮一種更為困難的情況,“缺了哪些值”。對於第1章中提到的學分保存方案中,有一個列出學生的student 表,一個列出已經出現過的學分事件的event 表,以及列出每個學生的每次學分事件學分的一個score 表。但是,如果一個學生在某個測試或測驗的同一天病了,那麼score 表中將不會有這個學生的該事件的學分,因此,要進行測驗或測試的補考。我們怎樣查找這些缺少了的記錄,以便能保證讓這些學生進行補考?問題是要對所有的學分事件確定哪些學生沒有某個學分事件的學分。換個說法,就是我們希望知道學生和事件的哪些組合不出現在學分表中。這就是我們希望LEFT JOIN 所做的事。這個連接不像前例中那樣簡單,因為我們不僅僅要查找不出現在單列中的值;還需要查找兩列的組合。
我們想要的這種組合是所有學生/事件的組合,它們由student 表與event 表的叉積產生:
FROM student, event
然後我們取出此連接的結果,與score 表執行一個LEFT JOIN 語句找出匹配者:
FROM student, event
LEFT JOIN score ON student.student_id = score.student.id
AND event.event_id = score.event_id
請注意,ON 子句使得score 表中的行根據不同表中的匹配者進行連接。這是解決本問題的關鍵。LEFT JOIN 強制為由student 和event 表的叉連接生成的每行產生一個行,即使沒有相應的score 表記錄也是這樣。這些缺少的學分記錄的結果行可通過一個事實來識別,就是來自score 表的列將全是NULL 的。我們可在WHERE 子句中選出這些記錄。來自score 表的任何列都是這樣,但因為我們查找的是缺少的學分,測試score 列從概念上可能最為清晰:
WHERE score.score IS NULL
可利用ORDER BY 子句對結果進行排序。兩種最合理的排序分別是按學生和按事件進行,我們選擇第一種: