天天看點

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

上節課我講到了聚集函數,以及如何對資料進行分組統計,可以說我們之前講的内容都是圍繞單個表的 SELECT 查詢展開的,實際上 SQL 還允許我們進行子查詢,也就是嵌套在查詢中的查詢。這樣做的好處是可以讓我們進行更複雜的查詢,同時更加容易了解查詢的過程。因為很多時候,我們無法直接從資料表中得到查詢結果,需要從查詢結果集中再次進行查詢,才能得到想要的結果。這個“查詢結果集”就是今天我們要講的子查詢。

通過今天的文章,我希望你可以掌握以下的内容:

  1. 子查詢可以分為關聯子查詢和非關聯子查詢。我會舉一個 NBA 資料庫查詢的例子,告訴你什麼是關聯子查詢,什麼是非關聯子查詢;
  2. 子查詢中有一些關鍵詞,可以友善我們對子查詢的結果進行比較。比如存在性檢測子查詢,也就是 EXISTS 子查詢,以及集合比較子查詢,其中集合比較子查詢關鍵詞有 IN、SOME、 ANY 和 ALL,這些關鍵詞在子查詢中的作用是什麼;
  3. 子查詢也可以作為主查詢的列,我們如何使用子查詢作為計算字段出現在 SELECT 查詢中呢?

什麼是關聯子查詢,什麼是非關聯子查詢

子查詢雖然是一種嵌套查詢的形式,不過我們依然可以依據子查詢是否執行多次,進而将子查詢劃分為關聯子查詢和非關聯子查詢。

子查詢從資料表中查詢了資料結果,如果這個資料結果隻執行一次,然後這個資料結果作為主查詢的條件進行執行,那麼這樣的子查詢叫做非關聯子查詢。

同樣,如果子查詢需要執行多次,即采用循環的方式,先從外部查詢開始,每次都傳入子查詢進行查詢,然後再将結果回報給外部,這種嵌套的執行方式就稱為關聯子查詢。

單說概念有點抽象,我們用資料表舉例說明一下。這裡我建立了 NBA 球員資料庫,SQL 檔案你可以從GitHub上下載下傳。

檔案中一共包括了 5 張表,player 表為球員表,team 為球隊表,team_score 為球隊比賽表,player_score 為球員比賽成績表,height_grades 為球員身高對應的等級表。

其中 player 表,也就是球員表,一共有 37 個球員,如下所示:

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

team 表為球隊表,一共有 3 支球隊,如下所示:

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

team_score 表為球隊比賽成績表,一共記錄了兩場比賽的成績,如下所示:

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

player_score 表為球員比賽成績表,記錄了一場比賽中球員的表現。這張表一共包括 19 個字段,代表的含義如下:

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

其中 shoot_attempts 代表總出手的次數,它等于二分球出手和三分球出手次數的總和。比如 2019 年 4 月 1 日,韋恩·艾靈頓在底特律活塞和印第安納步行者的比賽中,總出手次數為 19,總命中 10,三分球 13 投 4 中,罰球 4 罰 2 中,是以總分 score=(10-4)×2+4×3+2=26,也就是二分球得分 12+ 三分球得分 12+ 罰球得分 2=26。

需要說明的是,通常在工作中,資料表的字段比較多,一開始建立的時候會知道每個字段的定義,過了一段時間再回過頭來看,對當初的定義就不那麼确定了,容易混淆字段,解決這一問題最好的方式就是做個說明文檔,用執行個體舉例。

比如 shoot_attempts 是總出手次數(這裡的總出手次數 = 二分球出手次數 + 三分球出手次數,不包括罰球的次數),用上面提到的韋恩·艾靈頓的例子做補充說明,再回過頭來看這張表的時候,就可以很容易了解每個字段的定義了。

我們以 NBA 球員資料表為例,假設我們想要知道哪個球員的身高最高,最高身高是多少,就可以采用子查詢的方式:

SQL: SELECT player_name, height FROM player WHERE height = (SELECT max(height) FROM player)複制代碼
           

運作結果:(1 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

你能看到,通過SELECT max(height) FROM player可以得到最高身高這個數值,結果為 2.16,然後我們再通過 player 這個表,看誰具有這個身高,再進行輸出,這樣的子查詢就是非關聯子查詢。

如果子查詢的執行依賴于外部查詢,通常情況下都是因為子查詢中的表用到了外部的表,并進行了條件關聯,是以每執行一次外部查詢,子查詢都要重新計算一次,這樣的子查詢就稱之為關聯子查詢。比如我們想要查找每個球隊中大于平均身高的球員有哪些,并顯示他們的球員姓名、身高以及所在球隊 ID。

首先我們需要統計球隊的平均身高,即SELECT avg(height) FROM player AS b WHERE a.team_id = b.team_id,然後篩選身高大于這個數值的球員姓名、身高和球隊 ID,即:

SELECT player_name, height, team_id FROM player AS a WHERE height > (SELECT avg(height) FROM player AS b WHERE a.team_id = b.team_id)複制代碼
           

運作結果:(18 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

EXISTS 子查詢

關聯子查詢通常也會和 EXISTS 一起來使用,EXISTS 子查詢用來判斷條件是否滿足,滿足的話為 True,不滿足為 False。

比如我們想要看出場過的球員都有哪些,并且顯示他們的姓名、球員 ID 和球隊 ID。在這個統計中,是否出場是通過 player_score 這張表中的球員出場表現來統計的,如果某個球員在 player_score 中有出場記錄則代表他出場過,這裡就使用到了 EXISTS 子查詢,即EXISTS (SELECT player_id FROM player_score WHERE player.player_id = player_score.player_id),然後将它作為篩選的條件,實際上也是關聯子查詢,即:

SQL:SELECT player_id, team_id, player_name FROM player WHERE EXISTS (SELECT player_id FROM player_score WHERE player.player_id = player_score.player_id)複制代碼
           

運作結果:(19 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

同樣,NOT EXISTS 就是不存在的意思,我們也可以通過 NOT EXISTS 查詢不存在于 player_score 表中的球員資訊,比如主表中的 player_id 不在子表 player_score 中,判斷語句為NOT EXISTS (SELECT player_id FROM player_score WHERE player.player_id = player_score.player_id)。整體的 SQL 語句為:

SQL: SELECT player_id, team_id, player_name FROM player WHERE NOT EXISTS (SELECT player_id FROM player_score WHERE player.player_id = player_score.player_id)複制代碼
           

運作結果:(18 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

集合比較子查詢

集合比較子查詢的作用是與另一個查詢結果集進行比較,我們可以在子查詢中使用 IN、ANY、ALL 和 SOME 操作符,它們的含義和英文意義一樣:

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

還是通過上面那個例子,假設我們想要看出場過的球員都有哪些,可以采用 IN 子查詢來進行操作:

SELECT player_id, team_id, player_name FROM player WHERE player_id in (SELECT player_id FROM player_score WHERE player.player_id = player_score.player_id)複制代碼
           

你會發現運作結果和上面的是一樣的,那麼問題來了,既然 IN 和 EXISTS 都可以得到相同的結果,那麼我們該使用 IN 還是 EXISTS 呢?

我們可以把這個模式抽象為:

SELECT * FROM A WHERE cc IN (SELECT cc FROM B)複制代碼
           
SELECT * FROM A WHERE EXIST (SELECT cc FROM B WHERE B.cc=A.cc)複制代碼
           

實際上在查詢過程中,在我們對 cc 列建立索引的情況下,我們還需要判斷表 A 和表 B 的大小。在這裡例子當中,表 A 指的是 player 表,表 B 指的是 player_score 表。如果表 A 比表 B 大,那麼 IN 子查詢的效率要比 EXIST 子查詢效率高,因為這時 B 表中如果對 cc 列進行了索引,那麼 IN 子查詢的效率就會比較高。

同樣,如果表 A 比表 B 小,那麼使用 EXISTS 子查詢效率會更高,因為我們可以使用到 A 表中對 cc 列的索引,而不用從 B 中進行 cc 列的查詢。

了解了 IN 查詢後,我們來看下 ANY 和 ALL 子查詢。剛才講到了 ANY 和 ALL 都需要使用比較符,比較符包括了(>)(=)(=)(<=)和(<>)等。

如果我們想要查詢球員表中,比印第安納步行者(對應的 team_id 為 1002)中任何一個球員身高高的球員的資訊,并且輸出他們的球員 ID、球員姓名和球員身高,該怎麼寫呢?首先我們需要找出所有印第安納步行者隊中的球員身高,即SELECT height FROM player WHERE team_id = 1002,然後使用 ANY 子查詢即:

SQL: SELECT player_id, player_name, height FROM player WHERE height > ANY (SELECT height FROM player WHERE team_id = 1002)複制代碼
           

運作結果:(35 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

運作結果為 35 條,你發現有 2 個人的身高是不如印第安納步行者的所有球員的。

同樣,如果我們想要知道比印第安納步行者(對應的 team_id 為 1002)中所有球員身高都高的球員的資訊,并且輸出球員 ID、球員姓名和球員身高,該怎麼寫呢?

SQL: SELECT player_id, player_name, height FROM player WHERE height > ALL (SELECT height FROM player WHERE team_id = 1002)複制代碼
           

運作結果:(1 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

我們能看到比印第安納步行者所有球員都高的球員,在 player 這張表(一共 37 個球員)中隻有索恩·馬克。

需要強調的是 ANY、ALL 關鍵字必須與一個比較操作符一起使用。因為如果你不使用比較操作符,就起不到集合比較的作用,那麼使用 ANY 和 ALL 就沒有任何意義。

将子查詢作為計算字段

我剛才講了子查詢的幾種用法,實際上子查詢也可以作為主查詢的計算字段。比如我想查詢每個球隊的球員數,也就是對應 team 這張表,我需要查詢相同的 team_id 在 player 這張表中所有的球員數量是多少。

SQL: SELECT team_name, (SELECT count(*) FROM player WHERE player.team_id = team.team_id) AS player_num FROM team複制代碼
           

運作結果:(3 條記錄)

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

你能看到,在 player 表中隻有底特律活塞和印第安納步行者的球員資料,是以它們的 player_num 不為 0,而亞特蘭大老鷹的 player_num 等于 0。在查詢的時候,我将子查詢SELECT count(*) FROM player WHERE player.team_id = team.team_id作為了計算字段,通常我們需要給這個計算字段起一個别名,這裡我用的是 player_num,因為子查詢的語句比較長,使用别名更容易了解。

總結

今天我講解了子查詢的使用,按照子查詢執行的次數,我們可以将子查詢分成關聯子查詢和非關聯子查詢,其中非關聯子查詢與主查詢的執行無關,隻需要執行一次即可,而關聯子查詢,則需要将主查詢的字段值傳入子查詢中進行關聯查詢。

同時,在子查詢中你可能會使用到 EXISTS、IN、ANY、ALL 和 SOME 等關鍵字。在某些情況下使用 EXISTS 和 IN 可以得到相同的效果,具體使用哪個執行效率更高,則需要看字段的索引情況以及表 A 和表 B 哪個表更大。同樣,IN、ANY、ALL、SOME 這些關鍵字是用于集合比較的,SOME 是 ANY 的别名,當我們使用 ANY 或 ALL 的時候,一定要使用比較操作符。

最後,我講解了如何使用子查詢作為計算字段,把子查詢的結果作為主查詢的列。

SQL 中,子查詢的使用大大增強了 SELECT 查詢的能力,因為很多時候查詢需要從結果集中擷取資料,或者需要從同一個表中先計算得出一個資料結果,然後與這個資料結果(可能是某個标量,也可能是某個集合)進行比較。

a表兩個字段都與b表一個字段關聯_子查詢:子查詢的種類都有哪些,如何提高子查詢的性能?

我今天講解了子查詢的使用,其中講到了 EXISTS 和 IN 子查詢效率的比較,當查詢字段進行了索引時,主表 A 大于從表 B,使用 IN 子查詢效率更高,相反主表 A 小于從表 B 時,使用 EXISTS 子查詢效率更高,同樣,如果使用 NOT IN 子查詢和 NOT EXISTS 子查詢,在什麼情況下,哪個效率更高呢?

最後請你使用子查詢,編寫 SQL 語句,得到場均得分大于 20 的球員。場均得分從 player_score 表中擷取,同時你需要輸出球員的 ID、球員姓名以及所在球隊的 ID 資訊。

歡迎在評論區寫下你的思考,也歡迎點選請朋友讀把這篇文章分享給你的朋友或者同僚。

繼續閱讀