第13章 在查詢裡結合表
第14章 使用子查詢定義未確定數據
第15章 組合多個查詢
第13章 在查詢裡結合表
本章的重點包括:
簡介表的結合
不同類型的結合
如何、何時使用結合
表結合的範例
不恰當表結合的影響
在查詢中利用別名對表進行重命名
到目前為止,我們執行的數據庫查詢只是從一個表裡獲取數據。這一章將介紹如何在一個查詢裡結合多個表來獲取數據。
13.1 從多個表獲取數據
能夠從多個表選擇數據是SQL最強大的特性之一。如果沒有這種能力,關係型數據庫的整個概念就無法實現了。有時單表查詢就可以得到有用的信息,但在現實世界裡,最實用的查詢是要從數據庫裡的多個表獲取數據。
第 4 章已經介紹過,關係型數據庫為達到簡單和易於管理的目的,被分解為較小的、更易管理的表。正是由於表被分解為較小的表,它們通過共有字段(主鍵和外鍵)形成相互關聯的表,並且能夠通過這些字段結合在一起。
有人就會問了,既然最終還是要利用重新結合表來獲取需要的數據,那麼為什麼還要對表進行規格化呢?在實際應用中,我們很少會從表裡選擇全部數據,因此最好是根據每個查詢的需求進行挑選。雖然數據庫的規格化會對性能造成一點影響,但從整體來說,編程和維護都更加容易了。需要記住的是,規格化的主要目的是減少冗余和提高數據完整性。數據庫管理員的最終目標是確保數據安全。
13.2 結合的類型
結合是把兩個或多個表組合在一起來獲取數據。不同的實現具有多種結合表的方式,本章將介紹最常用的結合方式,它們是:
等值結合或內部結合;
非等值結合;
外部結合;
自結合。
13.2.1 結合條件的位置
從前面的課程可以知道,SELECT和FROM是SQL語句的必要子句;而在結合表時, WHERE子句是必要的。要結合的表列在FROM子句裡,而結合是在WHERE子句裡完成的。多個操作符可以用於結合表,比如=、<、>、<>、<=、>=、!=、BETWEEN、LIKE和NOT,其中最常用的是等於號。
13.2.2 等值結合
最常用也是最重要的結合就是等值結合,也被稱為內部結合。等值結合利用通用字段結合兩個表,而這個字段通常是每個表裡的主鍵。
等值結合的語法如下所示:
具體範例如下:
這個SQL語句返回僱員標識和僱傭日期。僱員標識來自於表EMPLOYEE_TBL(雖然它存在於兩個表裡,但我們必須指定一個表),而僱傭日期來自於表 EMPLOYEE_PAY_TBL。由於僱員標識在兩個表都存在,所以字段名稱前面必須用表名加以修飾,從而讓數據庫服務程序明確到哪裡獲取數據。
注意:在SQL語句中使用縮排
注意到在上面這個範例SQL語句裡使用了縮排方式來提高可讀性。縮排方式不是必須的,但是推薦使用。
下面的範例從表EMPLOYEE_TBL和EMPLOYEE_PAY_TBL裡獲取數據,使用了等值結合。
SELECT子句裡每個字段名稱都以表名作為前綴,從而準確標識各個字段。在查詢中,這被稱為限定字段,它只有在字段存在於多個表時才有必要。在調試或修改SQL代碼時,我們通常會對全部字段進行限定,從而提高一致性並減少問題。
另外,SQL裡可以利用 INNER JOIN語法來提高可讀性,如下所示:
在這種方式裡,WHERE 子句裡的結合操作符被去掉了,取而代之的是關鍵字 INNER JOIN。要被結合的表位於JOIN之後,而結合操作符位於關鍵字ON之後。下面的範例使用JOIN語法來返回與前例一樣的結果:
上述兩個範例的語法雖然不同,但它們都返回一樣的結果。
13.2.3 使用表的別名
使用表的別名意味著在SQL語句裡對表進行重命名,這是一種臨時性的改變,表在數據庫裡的實際名稱不會受到影響。稍後我們就會看到,讓表具有別名是完成自結合的必要條件。給表起別名一般是為了減少鍵盤輸入,從而得到更短、更易讀的SQL語句。另外,輸入較少就意味著更少的輸入錯誤。而且,在對別名進行引用時,由於它一般比較短,而且更能準確描述數據,所以編程錯誤也會更少。給表起別名同時也意味著被選擇字段必須用表的別名加以修飾。下面是使用表的別名的一些範例:
這個 SQL 語句裡給表設置了別名。EMPLOYEE_TBL 被重命名為 E,EMPLOYEE_PAY_TBL被重命名為EP。選擇什麼名稱作為別名沒有限制,這裡使用E是因為EMPLOYEE_TBL以E開頭。雖然EMPLOYEE_PAY_TBL也以E開頭,但不能再使用E了,所以用第一個字母E和第二個單詞的第一個字母(P)組成EP作為這個表的別名。被選擇的字段由相應表的別名加以修飾。注意WHERE子句裡使用的SALARY字段也必須用表的別名加以修飾。
13.2.4 不等值結合
不等值結合根據同一個字段在兩個表裡值不相等來實現結合,其語法如下所示:
具體範例如下:
下面的SQL語句返回在兩個表裡沒有相應記錄的全部僱員的標識及僱傭日期,使用的就是不等值結合:
警告:不等值組合可能會產生多餘數據
在使用不等值結合時,可能會得到很多無用的數據,其結果需要仔細檢查。
每個表裡只有 6 條記錄,上面這個 SQL 語句為什麼會返回 30 行記錄呢?對於表EMPLOYEE_TBL裡的每條記錄,在EMPLOYEE_PAY_TBL裡都有一條相應的記錄。由於在表結合時測試的是不相等條件,所以第一個表裡每條記錄在與第二個表裡的全部記錄進行比較時,除其對應的記錄,其他記錄都滿足條件。這意味著每條記錄都與第二個表裡5條不相關記錄滿足條件,因此6乘以5得到總共30條記錄。
在前面小節中使用等值結合的例子裡,第一個表裡的每條記錄都只與第二個表裡的一行記錄相匹配(其對應的記錄),所以6乘以1得到總共6條記錄。
13.2.5 外部結合
外部結合會返回一個表裡的全部記錄,即使對應的記錄在第二個表裡不存在。加號(+)用於在查詢裡表示外部結合,放在WHERE子句裡表名的後面。具有加號的表是沒有匹配記錄的表。在很多實現裡,外部結合被劃分為左外部結合、右外部結合和全外部結合。
注意:結合的語法結構多變
關於外部結合的使用與語法請查看具體實現的文檔。很多主流實現都使用“+”表示外部結合,但這並不是標準。實際上,相同數據庫實現的不同版本,其相關規定也不盡相同。例如,Microsoft SQL Server 2000支持這種語法,但其2005及以上版本卻不支持。所以,在使用這種語法結構時務必要小心。
外部結合的一般語法如下所示:
Oracle的語法是:
注意:外部結合的應用
外部結合只能用於JOIN條件的一側,但可以在JOIN條件裡對同一個表裡的多個字段進行外部結合。
外部結合的概念將在下面的兩個範例裡加以解釋。第一個範例選擇了產品描述和訂購數量,這兩個值取自兩個單獨的表裡。需要注意的是,並不是每件產品在表ORDERS_TBL裡都有相應的記錄。這裡執行了一個普通的等值結合:
這裡只得到了7種產品的17條記錄,但產品共有9種。我們想顯示全部的產品,不管它是否有訂單。
下面的範例通過使用外部結合來達到我們的目的,這裡使用的是Oracle語法:
這裡也可以使用前面介紹的比較繁瑣的語法結構,獲得相同的結果。下面的範例就使用了這種繁瑣結構,但清晰易懂。
這個查詢返回了全部的產品,不論它是否有相應的訂單。外部結合會包含表PRODUCT_TBL裡的全部記錄,不管它在表ORDER_TBL裡是否有對應的記錄。
13.2.6 自結合
自結合利用表別名在SQL語句對表進行重命名,像處理兩個表一樣把表結合到自身。其語法如下所示:
具體範例如下:
這個SQL語句返回表EMPLOYEE_TBL裡所有姓相同的僱員的姓名。當需要的數據都位於同一個表裡,而我們又必須對記錄進行一些比較時,就可以使用自結合。
還可以像下面這樣利用 INNER JOIN來得到同樣的結果:
使用自結合的另一個常見範例是:假設有一個表保存了僱員標識號碼、姓名、僱員主管的標識號碼。我們想列出所有僱員及其主管的姓名,問題在於僱員主管的姓名並不是表裡的一個字段:
在下面的語句裡,我們在FROM子句裡包含了表EMP兩次,讓表具有兩個別名。這樣我們就可以像使用兩個不同的表一樣進行操作。所有的主管也都是僱員,所以 JOIN 條件比較第一個表裡的僱員標識號碼與第二個表裡的主管標識號碼。第一個表就像是保存僱員信息的表,而第二個表就像是保存主管信息的表:
13.2.7 結合多個主鍵
大多數結合操作都會基於一個表裡的主鍵和另一個表裡的主鍵來合併數據。根據數據庫的設計情況,有時我們需要結合多個主鍵來描述數據庫裡的數據。比如可能某個表的主鍵由多個字段組成,可能某個表的外鍵由多個字段組成,分別引用多個主鍵。
比如下面這個Oracle表:
PROD裡的主鍵是由字段SERIAL_NUMBER和VENDOR_NUMBER組成的。也許兩個產品在配送公司具有相同的序列號,但在每個商家的序列號都是唯一的。
ORD裡的外鍵也是由字段SERIAL_NUMBER和VENDOR_NUMBER組成的。
在從兩個表(PROD和ORD)裡選擇數據時,結合操作可能是這樣的:
類似地,如果要使用 INNER JOIN,只需要在關鍵字ON之後列出多個結合操作:
13.3 需要考慮的事項
在使用結合之前需要考慮一些事情:基於什麼字段進行結合、是否有公用字段進行結合、性能問題。查詢裡的結合越多,數據庫需要完成的工作就越多,也就意味著需要越多的時間來獲取數據。在從規格化的數據庫裡獲取數據時,結合是不可避免的,但需要從邏輯角度來確定結合是正確執行的。不恰當的結合會導致嚴重的性能下降和不準確的查詢結果。關於性能的問題將在第18章詳細介紹。
13.3.1 使用基表
要結合什麼?如果需要從兩個表裡獲取數據,但它們又沒有公用字段,我們就必須結合另一個表,這個表與前兩個表都有公用字段,這個表就被稱為基表。基表用於結合具有公用字段的一個或多個表,或是結合沒有公用字段的多個表。下面是基表範例要用到的表:
假設我們要使用表CUSTOMERS_TBL和PRODUCTS_TBL,但它們之間沒有公用字段。現在來看表ORDERS_TBL,它與表 CUSTOMER_TBL 可以通過 CUST_ID字段結合,與表PRODUCTS_TBL可以通過PROD_ID字段結合。相應的JOIN條件及結果如下所示:
注意:別名的使用
注意WHERE子句裡的表別名和它們如何用於字段。
13.3.2 笛卡爾積
笛卡爾積是笛卡爾結合或“無結合”的結果。如果從兩個或多個沒有結合的表裡獲取數據,輸出結果就是所有被選表裡的全部記錄。如果表的規模很大,其結果可能是幾十萬,甚至是數百萬行數據。因此,在從兩個或多個表裡獲取數據時,強烈建議使用WHERE子句。笛卡爾積通常也被稱為交叉結合。
其語法如下所示:
下面是交叉結合(或稱為可怕的笛卡爾積)的範例:
雖然沒有執行 JOIN 操作,數據還是取自兩個單獨的表。由於我們沒有指定第一個表裡的記錄如何與第二個表裡的記錄相結合,數據庫服務程序把第一個表裡每行記錄都與第二個表裡的全部記錄相匹配。每個表都有6條記錄,所以最終結果是6乘以6共計36條記錄。
為了更好地理解笛卡爾積是如何得到的,再看下面這個範例:
警告:務必確保所有的表都結合完畢
在查詢裡結合多個表要特別小心。如果查詢裡的兩個表沒有結合,而且每個表都包含 1 000 行數據,那麼笛卡爾積就會是 1 000 乘以 1 000,也就是1 000 000 行數據。在處理大量數據時,笛卡爾積有時會導致主機停止或崩潰。因此,對於DBA和系統管理員來說,密切監視長時間運行的查詢是件很重要的工作。
13.4 小結
本章介紹了SQL最強大的功能之一:表的結合。想像一下,如果在查詢裡只能從一個表獲取數據,那我們將受到多麼大的局限。這裡介紹了多種結合類型,它們分別具有自己的功能。內部結合可以根據相等或不相等的條件連接多個表裡的數據。外部結合是相當強大的,即使在被結合的表沒有匹配數據時,也能從中獲取數據。自結合用於把表與自身相結合。對於交叉結合,也就是笛卡爾積,要特別小心,它是多個表沒有進行任何結合的結果,經常會產生大量不必要的結果。因此,在從多個表裡獲取數據時,一定要根據相關聯的字段(通常是主鍵)把表進行結合。如果沒有恰當地對表進行結合,可能會產生不完整或不正確的輸出結果。
13.5 問與答
問:在結合表時,它們的結合次序必須與它們在FROM子句裡出現的次序一樣嗎?
答:不必,它們不必以同樣的次序出現。但是,表在FROM裡的次序和表被結合的次序可能會對性能有所影響。
問:在使用基表結合沒有關聯的表時,必須從基表裡選擇字段嗎?
答:不必。使用基表結合不相關的表並不要求從基表裡選擇字段。
問:在結合表時可以基於多個字段嗎?
答:可以。有些查詢要求基於多個字段進行結合,才能描述表的記錄之間的完整關係。
13.6 實踐
下面的內容包含一些測試問題和實戰練習。這些測試問題的目的在於檢驗對學習內容的理解程度。實戰練習有助於把學習的內容應用於實踐,並且鞏固對知識的掌握。在繼續學習之前請先完成測試與練習,答案請見附錄C。
13.6.1 測驗
1.如果不論相關表裡是否存在匹配的記錄,都要從表裡返回記錄,應該使用什麼類型的結合?
2.JOIN條件位於SQL語句的什麼位置?
3.使用什麼類型的結合來判斷相關表的記錄之間的相等關係?
4.如果從兩個不同的表獲取數據,但它們沒有結合,會產生什麼結果?
5.使用如下的表:
下面使用外部結合的語法正確嗎?
如果使用繁瑣語法,上述查詢語句會是什麼樣子?
13.6.2 練習
1.在數據庫中輸入以下代碼,研究得到的結果(笛卡爾積):
2.輸入以下命令來結合表EMPLOYEE_TBL和EMPLOYEE_PAY_TBL:
3.改寫練習2里的SQL查詢語句,使用 INNER JOIN語法。
4 .編寫一個 SQL 語句,從表 EMPLOYEE_TBL 返回 EMP_ID、LAST_NAME 和FIRST_NAME字段,從表EMPLOYEE_PAY_TBL返回SALARY和BONUS字段。使用兩種類型的 INNER JOIN技術。完成上述查詢以後,再進一步計算出每個城市僱員的平均薪水是多少。
5.嘗試自己編寫幾條使用結合操作的查詢語句。
第14章 使用子查詢定義未確定數據
本章的重點包括:
什麼是子查詢
使用子查詢的原因
常規數據庫查詢中使用子查詢的範例
子查詢與數據操作命令
嵌入式子查詢
本章介紹子查詢的相關內容。子查詢可以幫助用戶更便捷地完成複雜的查詢操作。
14.1 什麼是子查詢
子查詢也被稱為嵌套查詢,是位於另一個查詢的 WHERE 子句裡的查詢,它返回的數據通常在主查詢裡作為一個條件,從而進一步限制數據庫返回的數據。它可以用於 SELECT、INSERT、UPDATE和DELETE語句。
在某些情況下,子查詢能夠間接地基於一個或多個條件把多個表裡的數據關聯起來,從而代替結合操作。當查詢裡使用子查詢時,子查詢首先被執行,然後主查詢再根據子查詢返回的結果執行。子查詢的結果用於在主查詢的 WHERE 子句裡處理表達式。子查詢可以用於主查詢的WHERE子句或HAVING子句。邏輯和關係操作符,比如=、>、<、<>、!=、IN、NOT IN、AND、OR,可以用於子查詢裡,也可以在WHERE或HAVING子句裡對子查詢進行操作。
注意:子查詢規則
標準查詢的規則同樣也適用於子查詢,結合操作、函數、轉換和其他選項都可以在子查詢裡使用。
注意:使用縮進來提高可讀性
注意範例中所使用的縮進。使用縮進基本上就是為了提高可讀性。我們發現在查找SQL語句裡的錯誤時,語句越整潔,就越容易閱讀並發現語法中的錯誤。
子查詢必須遵循以下規則。
子查詢必須位於圓括號裡。
除非主查詢裡有多個字段讓子查詢進行比較,否則子查詢的SELECT子句裡只能有一個字段。
子查詢裡不能使用ORDER BY子句。在子查詢裡,我們可以利用GROUP BY子句實現ORDER BY功能。
返回多條記錄的子查詢只能與多值操作符(比如IN)配合使用。
SELECT列表裡不能引用任何BLOB、ARRAY、CLOB或NCLOB類型的值。
子查詢不能直接被包圍在函數里。
操作符BETWEEN不能用於子查詢,但子查詢內部可以使用它。子查詢的基本語法如下所示:
下面的範例展示了操作符 BETWEEN 與子查詢的關係。首先是在子查詢裡使用BETWEEN的正確範例。
不能夠在子查詢外使用BETWEEN。下面是錯誤地把BETWEEN用於子查詢的範例:
14.1.1 子查詢與SELECT語句
雖然子查詢也可以用於數據操作語句,但它最主要還是用於SELECT語句裡,獲取數據給主查詢使用。
基本語法如下所示:
下面是一個範例:
上面這條SQL語句返回小時工資低於僱員443679012的所有僱員的標識、姓、名和小時工資。這時,我們不必準確知道(或關心)這個特定僱員的小時工資是多少,只想知道比這個僱員工資低的人都是誰。
注意:使用子查詢來查找不確定的值
在不能確定條件裡的準確數值時,通常可以使用子查詢來實現。僱員220984332的薪水是不確定的,但子查詢可以幫我們完成這些跑腿的工作。
下面的查詢選擇某個僱員的小時工資,這個查詢將作為後面範例裡的一個子查詢。
前面的查詢在下面查詢的WHERE子句裡充當一個子查詢:
子查詢的結果是11(見前一個範例),所以上面這個WHERE子句的條件實際上是:
在執行這個查詢時,我們不知道特定僱員的小時工資是多少,但主查詢還是可以把每個僱員的小時工資與子查詢的結果進行比較。
14.1.2 子查詢與INSERT語句
子查詢可以與數據操作語言(DML)配合使用。首先是INSERT語句,它將子查詢返回的結果插入到另一個表。我們可以用字符函數、日期函數或數值函數對子查詢裡選擇的數據進行調整。
注意:提交執行DML命令
在使用像 INSERT 語句這樣的 DML 命令時,要記得使用 COMMIT 和ROLLBACK命令。
基本語法如下所示:
下面是在INSERT語句裡使用子查詢的範例:
這個 INSERT 語句把小時工資高於僱員 220984332 的所有僱員的 EMP_ID、LAST_NAME、FIRST_NAME和PAY_RATE插入到一個名為RICH_EMPLOYEES的表裡。
14.1.3 子查詢與UPDATE語句
子查詢可以與UPDATE語句配合使用來更新一個表裡的一個或多個字段,其基本語法如下所示:
下面的範例展示了如何在 UPDATE 語句裡使用子查詢。第一個查詢返回居住在Indianapolis的全部僱員的標識,可以看到共有4人滿足條件。
前面這個查詢作為一個子查詢用於下面這個UPDATE語句裡。前面的結果說明了子查詢會返回的僱員數量。下面是使用這個子查詢的UPDATE語句:
不出所料,有4條記錄被更新了。與前一小節的子查詢範例不同的是,這個子查詢返回多條記錄,因此要使用操作符IN而不是等號(IN可以把一個表達式與列表裡的多個值進行比較)。這裡如果使用了等號,數據庫會返回一個錯誤消息。
14.1.4 子查詢與DELETE語句
子查詢也可以與DELETE語句配合使用,其基本語法如下所示:
下面的範例從表EMPLOYEE_PAY_TBL裡刪除GRANDON GLASS的記錄。這時我們不知道Brandon的標識號碼,但可以利用一個子查詢,根據FIRST_NAME和LAST_NAME字段的值從表EMPLOYEE_TBL裡獲取他的標識號碼。
14.2 嵌套的子查詢
子查詢可以嵌入到另一個子查詢裡,就像子查詢嵌套在普通查詢裡一樣。在有子查詢時,子查詢先於主查詢執行。類似地,在嵌套的子查詢裡,最內層的子查詢先被執行,然後再依次執行外層的子查詢,直到主查詢。
注意:確認實現對子查詢的限制規定
一個語句裡能夠嵌套的子查詢的數量取決於具體的實現,請查看相應的文檔。
嵌套子查詢的基本語法如下所示:
下面的範例使用了兩個子查詢,一個嵌套在另一個之內。這個範例返回一些顧客的信息,這些顧客的訂單的數量乘以單個訂單的結果大於全部產品的價格總和。
警告:使用WHERE子句
不要忘記在UPDATE和DELETE語句裡使用WHERE子句,否則目標表裡的全部數據都會被更新或刪除。可以先使用一個帶有 WHERE 子句的SELECT語句進行查詢,以便確認所要操作的數據準確無誤。詳情請見第5章的內容。
共有6條記錄滿足兩個子查詢的條件。
下面分別是兩個子查詢的結果,可以幫助我們更好地理解主查詢是如何運行的。
當最內層子查詢執行完成之後,主查詢實際上就變成這樣:
當外層子查詢也執行完成之後,主查詢就是這樣了:
下面是最終的結果:
警告:多個子查詢可能會產生問題
使用多個子查詢可能會延長響應時間,還可能降低結果的準確性,因為代碼裡可能存在錯誤。
14.3 關聯子查詢
關聯子查詢在很多SQL實現裡都存在,它的概念屬於ANSI標準。關聯子查詢是依賴主查詢裡的信息的子查詢。這意味著子查詢裡的表可以與主查詢裡的表相關聯。
在下面這個範例裡,子查詢裡結合的表CUSTOMER_TBL和ORDERS_TBL依賴於主查詢裡CUSTOMER_TBL的別名(C)。這個查詢返回訂購超過10件物品的顧客的姓名。
下面這個語句對子查詢進行了一點修改,顯示每個顧客訂購的物品數量。
在這個範例裡,GROUP BY子句是必需的,因為另一個字段被匯總函數SUM使用了。這樣我們就得到了每個顧客訂購的數量總和。在前一個子查詢裡,SUM函數用於獲得整個查詢的總和,就不是必須使用GROUP BY子句了。
14.4 子查詢的效率
子查詢會對執行效率產生影響。在應用子查詢前,必須首先考慮好其所帶來的影響。由於子查詢會在主查詢之前進行,所以子查詢所花費的時間,會直接影響整個查詢所需要的時間。看下面的範例。
注意:適當使用關聯子查詢
在進行關聯子查詢時,如果要在子查詢中使用某個表,必須首先在主查詢中引用這個表。
如果PRODUCTS_TBL表中包含有數以千計的產品信息,而ORDERS_TBL表中則保存了數以百萬計的訂單信息,想像一下這將意味著什麼。對PRODUCTS_TBL表進行匯總,並與ORDERS_TBL進行關聯,將在很大程度上影響操作的運行速度。所以,在需要使用子查詢從數據庫中獲得相應信息的時候,務必考慮清楚子查詢的執行效率。
14.5 小結
簡單來說,子查詢就是在另一個查詢裡執行的查詢,用於進一步設置查詢的條件。子查詢可以用於SQL語句的WHERE子句或HAVING子句。它不僅可以在查詢裡使用,還可以用於DML(數據操作語言)語句,比如INSERT、UPDATE和DELETE,但這時要注意遵守DML的基本規則。
子查詢的語法實質上與普通查詢是一樣的,只是有一些細微的限制。其中之一是不能使用ORDER BY子句,但可以使用GROUP BY子句,也能得到同樣的效果。子查詢可以向查詢提供不必事先確定的條件,增強了SQL的功能靈活性。
14.6 問與答
問:在子查詢的範例裡有很多的縮進,這是語法要求的嗎?
答:當然不是,縮進只是把語句劃分為多個部分,讓語句更易於閱讀和理解。
問:一個查詢裡能夠嵌套的子查詢數量是否有限制?
答:像允許嵌套的子查詢數量、查詢裡能夠結合的表的數量等限制都是取決於具體實現的。有些實現可能沒有限制,但子查詢嵌套太多可能會明顯降低語句的性能。大多數限制受到實際的硬件、CPU速度和可用系統內存的影響,當然還有其他一些考慮。
問:調試具有子查詢,特別是嵌套子查詢的語句似乎很容易讓人迷惑,有什麼好方法來調試具有子查詢的語句嗎?
答:調試具有子查詢的語句的最好方法是分幾個部分對查詢進行求值。首先,運算最內層的子查詢,然後逐步擴展到主查詢(這與數據庫執行查詢的次序一樣)。在單獨運行了每個子查詢之後,就可以把子查詢的返回值代入到主查詢,檢查主查詢的邏輯是否正確。子查詢帶來的錯誤經常是由對其使用的操作符造成的,比如=、IN、<、>等。
14.7 實踐
下面的內容包含一些測試問題和實戰練習。這些測試問題的目的在於檢驗對學習內容的理解程度。實戰練習有助於把學習的內容應用於實踐,並且鞏固對知識的掌握。在繼續學習之前請先完成測試與練習,答案請見附錄C。
14.7.1 測驗
1.在用於SELECT語句時,子查詢的功能是什麼?
2.在子查詢與UPDATE語句配合使用時,能夠更新多個字段嗎?
3.下面的語法正確嗎?如果不正確,正確的語法應該是怎樣?
a.
b.
c.
4.下面語句執行的結果是什麼?
14.7.2 練習
1.編寫SQL的子查詢代碼,與書中提供的進行比較。使用下面的表來完成練習。
2.使用子查詢編寫一個SQL語句來更新表CUSTOMER_TBL,找到ORD_NUM列中訂單號碼為 23E934的顧客,把顧客名稱修改為DAVIDS MARKET。
3.使用子查詢編寫一個SQL語句,返回小時工資高於 JOHN DOE的全部僱員的姓名;JOHN DOE的僱員標識號碼是 343559876。
4.使用子查詢編寫一個SQL語句,列出所有價格高於全部產品平均價格的產品。
第15章 組合多個查詢
本章的重點包括:
簡介用於組合查詢的操作符
何時對查詢進行組合
GROUP BY子句與組合命令
ORDER BY與組合命令
如何獲取準確的數據
本章介紹如何使用操作符UNION、UNION ALL、INTERSECT和EXCEPT把多個SQL查詢組合為一個。同樣的,這些操作符的實際使用方法請參考具體實現的文檔。
15.1 單查詢與組合查詢
單查詢是一個SELECT語句,而組合查詢具有兩個或多個SELECT語句。
組合查詢由負責結合兩個查詢的操作符組成,下面的範例使用操作符UNION結合兩個查詢。
單個SQL語句的範例:
下面是同一個語句使用操作符UNION:
上面的語句返回所有僱員的工資信息,包含月薪和小時工資。
組合操作符用於組合和限制兩個SELECT語句的結果,它們可以返回或清除重複的記錄。組合操作符可以獲取不同字段裡的類似數據。
注意:UNION操作符如何起作用
第二個查詢的輸出結果裡有兩個列標題:EMP_ID 和 SALARY,每個人的工資都列在SALARY之下。在使用UNION操作符時,列標題是由SELECT語句裡的字段名稱或字段別名決定的。
組合查詢可以把多個查詢的結果組合為一個數據集,而且通常比使用複雜條件的單查詢更容易編寫。另外,組合查詢對於數據檢索也具有更強的靈活性。
15.2 組合查詢操作符
不同數據庫廠商提供的組合操作符略有不同。ANSI標準包括 UNION、UNION ALL、EXCEPT和INTERSECT,下面的小節將分別討論這些操作符。
15.2.1 UNION
UNION 操作符可以組合兩個或多個 SELECT 語句的結果,不包含重複的記錄。換句話說,如果某行的輸出存在於一個查詢結果裡,那麼其他查詢結果同一行的記錄就不會再輸出了。在使用UNION操作符時,每個SELECT語句裡必須選擇同樣數量的字段、同樣數量的字段表達式、同樣的數據類型、同樣的次序——但長度不必一樣。
語法如下:
比如下面這個範例:
僱員ID在兩個表裡都存在,但在結果裡只出現一次。
本章的範例由從兩個表獲取數據的簡單SELECT語句開始:
現在利用UNION操作符組合上述兩個查詢,構造一個組合查詢:
注意:創建表PRODUCTS_TBL
表PRODUCTS_TBL是在第3章裡創建的。
第一個查詢返回11條數據,第二個查詢返回11條數據,但使用UNION操作符組合兩個查詢之後只返回了11條數據,這是因為UNION不會返回重複的數據。
下面的範例使用UNION操作符組合兩個不相關的查詢:
PROD_DESC和LAST_NAME的值被列在一起,列標題來自於第一個查詢的字段名稱。
15.2.2 UNION ALL
UNION ALL操作符可以組合兩個SELECT語句的結果,並且包含重複的結果。其使用規則與UNION一樣,它與UNION基本上是一樣的,只是一個返回重複的結果,一個不返回。
基本語法如下所示:
下面這個SQL語句返回全部僱員的ID,並且包含重複的記錄:
下面是使用UNION ALL操作符改寫前一小節的組合查詢:
因為UNION ALL操作符會返回重複的數據,所以這個查詢返回了 22條記錄(11+11)。
15.2.3 INTERSECT
INTERSECT 可以組合兩個 SELECT 語句,但只返回第一個 SELECT 語句裡與第二個SELECT語句裡一樣的記錄。其使用規則與UNION操作符一樣。目前MySQL5.0尚不支持INTERSECT,但SQL Server和Oracle全都提供支持。
基本語法如下所示:
範例如下:
前面這個SQL語句返回具有訂單的顧客的ID。
下面的範例使用INTERSECT組合兩個查詢:
這裡只返回了11條記錄,因為兩個查詢之間只有11條記錄是一樣的。
15.2.4 EXCEPT
EXCEPT 操作符組合兩個 SELECT 語句,返回第一個 SELECT 語句裡有但第二個SELECT語句裡沒有的記錄。同樣的,它的使用規則與UNION操作符一樣。目前MySQL並不支持EXCEPT。而在Oracle中,則使用MINUS操作符來實現同樣的功能。
其語法如下所示:
觀察下面SQL Server實現中的範例:
根據結果可以瞭解到,有3條記錄存在於第一個查詢的結果且不存在於第二個查詢的結果。
下面的範例展示了以MINUS代替EXCEPT。
15.3 組合查詢裡使用ORDER BY
ORDER BY子句可以用於組合查詢,但它只能用於對全部查詢結果的排序,因此組合查詢裡雖然可能包含多個查詢或SELECT語句,但只能有一個ORDER BY子句,而且它只能以別名或數字來引用字段。
其語法如下所示:
下面這個範例從EMPLOYEE_TBL表和EMPLOYEE_PAY_TBL表中返回僱員ID,但是不顯示重複記錄,返回結果根據EMP_ID排序:
注意:在ORDER BY子句中使用數字
ORDER BY子句裡的字段是以數字1進行引用的,沒有什麼實際的字段名稱。
組合查詢的結果以每個查詢的第一個字段進行排序。在排序之後,重複的記錄就很明顯了。
下面的範例在組合查詢裡使用ORDER BY子句。如果排序的字段在全部查詢語句裡都具有相同的名稱,它的名稱就可以用於ORDER BY子句裡。
下面的查詢在ORDER BY子句裡以數據代表字段:
15.4 組合查詢裡使用GROUP BY
與ORDER BY不同的是,GROUP BY子句可以用於組合查詢中的每一個 SELECT語句,也可以用於全部查詢結果。另外,HAVING子句也可以用於組合查詢裡的每個SELECT語句。
其語法如下所示:
下面的查詢利用一個字符串代表顧客記錄、僱員記錄和產品記錄。每個單獨的查詢就是統計表裡的記錄總數。GROUP BY子句用於把整個結果根據第一個字段進行分組。
下面的查詢與前一個一樣,只是使用了ORDER BY子句:
它根據每個表裡的第二列進行排序,因此輸出結果根據總數從小到大排列。
注意:錯誤數據
不完整的查詢返回結果被稱為錯誤數據。
15.5 獲取準確的數據
使用組合查詢時要小心。在使用INTERSECT操作符時,如果第一個查詢的SELECT語句有問題,就可能會得到不正確或不完整的數據。另外,在使用UNION和UNION ALL操作符時,要考慮是否需要返回重複的數據。那EXCEPT呢?我們是否需要不存在於第二個查詢裡的數據?很明顯,組合查詢裡的錯誤組合操作符或單個查詢的次序有誤都會導致返回不正確的數據。
15.6 小結
本章介紹了組合查詢。之前介紹的SQL語句都是構成單個查詢,而組合查詢可以讓多個查詢一起返回一個統一的數據集。這裡討論的組合操作符包括 UNION、UNION ALL、INTERSECT和EXCEPT(MINUS)。UNION返回兩個查詢的結果,不包含重複記錄。UNION ALL會返回兩個查詢的全部結果,不管數據是否重複。INTERSECT返回兩個查詢結果中一樣的記錄。EXCEPT(MINUS)返回一個查詢結果中不存在於另一個查詢結果的記錄。組合查詢具有很大的靈活性,能夠滿足各種查詢的要求。如果不使用組合查詢,可能需要很複雜的查詢語句才能達到同樣的結果。
15.7 問與答
問:組合查詢中的GROUP BY子句如何引用字段?
答:如果被引用的字段在所有查詢裡都是相同的名稱,就可以直接使用字段名稱進行引用;否則可以使用字段在SELECT語句裡的次序號碼進行引用。
問:在使用EXCEPT操作符時,如果顛倒SELECT語句的次序是否會改變輸出結果呢?
答:是的。在使用EXCEPT或MINUS操作符時,單個查詢的次序是很重要的。返回的數據是存在於第一個查詢結果且不存在於第二個查詢結果的記錄,所以改變單個查詢的次序肯定會改變結果。
問:組合查詢裡的單個查詢的字段是否一定要具有同樣的數據類型和長度?
答:不,只有數據類型要求是一樣的,長度可以不同。
問:使用UNION操作符時,字段名稱是由什麼決定的?
答:在使用UNION操作符時,第一個查詢決定了輸出的字段名稱。
15.8 實踐
下面的內容包含一些測試問題和實戰練習。這些測試問題的目的在於檢驗對學習內容的理解程度。實戰練習有助於把學習的內容應用於實踐,並且鞏固對知識的掌握。在繼續學習之前請先完成測試與練習,答案請見附錄C。
15.8.1 測驗
在下面的練習裡使用INTERSECT或EXCEPT操作符時,請參考本章介紹的語法。請注意,MySQL目前還不支持這兩個操作符。
1.下面組合查詢的語法正確嗎?如果不正確,請修改它們。它們使用的表EMPLOYEE_TBL和EMPLOYEE_PAY_TBL如下所示:
a.
b.
c.
2.匹配操作符與相應的描述。
描述 操作符
a.顯示重複記錄 UNION
b.返回第一個查詢裡與第二個查詢匹配的結果 INTERSECT
c.返回不重複的記錄 UNION ALL
d.返回第一個查詢裡有但第二個查詢沒有的結果 EXCEPT
15.8.2 練習
下面的練習請參考本章介紹的語法。由於 MySQL 不支持本章介紹的兩個操作符,所以請自行編寫查詢語句,並與書中提供的進行比較。
使用的表CUSTOMER_TBL和ORDERS_TBL如下所示:
1.編寫一個組合查詢,返回下了訂單的顧客。
2.編寫一個組合查詢,返回沒有下訂單的顧客。