從多個表中檢索記錄

你把記錄放在數據庫中並沒有什麼好處,除非你從中檢索它們,並對它們進行處理。這就是SELECT語句的目的--幫助你獲得你的數據。SELECT可能是SQL語句中使用最頻繁的語句了,但是它也可能是最需要技巧的一個;你選擇使用的限制條件可能很複雜,並且涉及到對許多表中的列進行比較。

SELECT語句的基本語法看起來像:

SELECT selection_list                  # What columns to select
FROM table_list                        # Where to select rows from
WHERE primary_constraint               # What conditions rows must satisfy
GROUP BY grouping_columns              # How to group results
ORDER BY sorting_columns               # How to sort results
HAVING secondary_constraint            # Secondary conditions rows must satisfy
LIMIT count;                           # Limit on results</span>

在這個語法中所有的都是可選的,除了SELECT和指定你想要檢索的selection_list部分。一些數據庫也需要FROM子句。但是MySQL並不需要,這就使得你可以在不指定任何表的前提下去評估表達式:

SELECT SQRT(POW(3,2)+POW(4,2));
在第一章中,我們專注於單表的SELECT語句,主要集中在輸出列,以及WHERE, GROUP BY, ORDER BY,和LIMIT。這一章我們包括SELECT經常混淆的一部分--joins語句;也就是SELECT從多個表中檢索記錄。我們將討論MySQL支持的join類型,它們是什麼以及如何指定它們。這應該幫助你更高效地部署MySQL,因爲在許多情況下,指出如何寫一個查詢語句的實際問題是由合適的將表加在一起的方式決定的。

使用SELECT的一個問題是,當你第一次遇到了一個新的問題,並不總是很容易去寫一個SELECT查詢語句去解決問題。但是,當你解決了之後,你在未來遇到相似的問題後,你可以根據你的經驗去解決問題。想要高效地使用SELECT語句,過往的經驗可能起着最大的作用。因爲它應用在各種各樣的問題上。

隨着你經驗的增長,會變得很容易接受使用join去解決新的問題。你會發現你自己思考問題像“是的,那是一個left join的事”,或者,“那是一個通過限制關鍵列的公共對的三方的join”。(我有點不情願地指出,實際上,你可能發現經驗幫助了你會顯得振奮人心。另一方面,你可能發現你可能結束以那樣的方式去思考是令人震驚的!)

許多例子以如下的兩個表t1和t2來說明如何使用MySQL支持的join操作。它們很小,使得它們足夠簡單一眼看到join對每個類型的效果。

Table t1:   Table t2:
+----+----+  +----+----+
| i1 | c1 |  | i2 | c2 |
+----+----+  +----+----+
| 1  | a  |  | 2  | c  |
| 2  | b  |  | 3  | b  |
| 3  | c  |  | 4  | a  |
+----+----+  +----+----+

談不上join的join

最簡單的join是微不足道的join,在這裏僅僅只有一個表。在這個例子中,從命名的表中選出行。

mysql> SELECT * FROM t1;
+----+----+
| i1 | c1 |
+----+----+
| 1  | a  |
| 2  | b  |
| 3  | c  |
+----+----+

一些人根本不認爲這是一個SELECT形式的join,他們認爲join僅僅是針對兩到三個表的SELECT。我想這只是一個認知角度的問題。

 Full join(全連接)

如果SELECT語句選擇多個表在from從句中,並以逗號分隔多個表,MySQL就執行一個全連接。例如,如果你join t1和t2如下所示,t1中的每一行與t2中的每一行進行組合。

mysql> select t1.*, t2.* from t1, t2;
+------+------+------+------+
| i1   | c1   | i1   | c2   |
+------+------+------+------+
|    1 | a    |    2 | c    |
|    2 | b    |    2 | c    |
|    3 | c    |    2 | c    |
|    1 | a    |    3 | b    |
|    2 | b    |    3 | b    |
|    3 | c    |    3 | b    |
|    1 | a    |    4 | a    |
|    2 | b    |    4 | a    |
|    3 | c    |    4 | a    |
+------+------+------+------+
一個全連接也被稱作cross連接,因爲每一個表的每一行與另一個表的每一行相交叉來產生所有可能的組合。這就是著名的笛卡爾乘積。像這樣連接表會潛在地產生非常大的行數,因爲可能的行數是在每個表中的行數的乘積。一個三個表的全連接,每個表分別包含100,200,300行,則會返回一個100x200x300=6百萬行的結果。即使對每一個表來說行數都不大,但是產生的結果卻很大。在這種情況下,where語句通常會被用來減少結果的集合到一個可操作的大小。

如果你增加一個where從句導致表只匹配在特定列的值上,join就變成了所謂的equi-join,因爲你只選擇在指定列上有相等值的行:

mysql> SELECT t1.*, t2.* FROM t1, t2 WHERE t1.i1 = t2.i2;
+----+----+----+----+
| i1 | c1 | i2 | c2 |
+----+----+----+----+
| 2  | b  | 2  | c  |
| 3  | c  | 3  | b  |
+----+----+----+----+
Join和cross join和','(逗號)連接符可看做是一樣的。例如,如下語句的結果是完全一樣的:

SELECT t1.*, t2.* FROM t1,t2 WHERE t1.i1 = t2.i2;
SELECT t1.*, t2.* FROM t1 JOIN t2 WHERE t1.i1 = t2.i2;
SELECT t1.*, t2.* FROM t1 CROSS JOIN t2 WHERE t1.i1 = t2.i2;
通常情況下,在掃描表來最快地檢索行時,MySQL優化器會自行決定順序。偶爾優化器也會做出非最優的選擇。如果你發現了,又可以通過使用STRIGHT_JOIN關鍵字來覆蓋優化器的選擇。Join執行有STRIGHT_JOIN時像一個cross join,但是會迫使表以from中的順序來被連接。

STRIGHT_JOIN可以在SELECT語句中的兩個地方被指定。你可以指定它在介於SELECT關鍵字和選擇列表之間,這樣會有一個全局效果在所有的cross joins。你也可以在FROM從句中指定它。如下的兩種語句是等價的:

SELECT STRAIGHT_JOIN ... FROM t1, t2, t3 ... ;
SELECT ... FROM t1 STRAIGHT_JOIN t2 STRAIGHT_JOIN t3 ... ;

左連接和右連接

等連接(equi-join)僅顯示在兩個表中都能匹配的行。左連接和右連接也顯示匹配行,同時也會有僅出現在一個表中而另外一個表沒有匹配的行。在這裏以左連接(LEFT JOIN)爲例,會識別在左表中但並沒有匹配在右表中的行。右連接除了表換一下以外,別的都一樣。

左連接像這樣工作:你指定被用來匹配行在兩個表中的列。當左表的一行匹配了右表的一行時,該行的內容會被選擇並輸出。當左表的行沒有在右表中找到匹配,它仍然會被選擇並輸出,但是會連接一個假的行從右表中,並且第二個表中所有的列被設置爲NULL。換句話說,左連接會強制結果集包含左表中的所有行,不管是否在右表中有匹配的行。沒有被匹配的行可以被右表中的所有列被設置爲NULL所識別。

再一次考慮如下兩個表,t1和t2:

Table t1:   Table t2:
+----+----+  +----+----+
| i1 | c1 |  | i2 | c2 |
+----+----+  +----+----+
| 1  | a  |  | 2  | c  |
| 2  | b  |  | 3  | b  |
| 3  | c  |  | 4  | a  |
+----+----+  +----+----+
如果我們使用交叉連接來匹配這些表在t1.i1和t2.i2上,我們僅會得到輸出爲在兩個表中都出現的值2和值3.
mysql> select t1.*, t2.* from t1, t2 where t1.i1 = t2.i2;
+------+------+------+------+
| i1   | c1   | i2   | c2   |
+------+------+------+------+
|    2 | b    |    2 | c    |
|    3 | c    |    3 | b    |
+------+------+------+------+
左連接會輸出表t1中的每一行,而不管是否表2中有與它匹配的行。要寫一個左連接,通過將LEFT JOIN介於兩個表之間,而不是一個逗號,使用ON從句來指定匹配條件(而不是WHERE從句)

mysql> select t1.*, t2.* from t1 LEFT JOIN t2 on t1.i1=t2.i2;
+------+------+------+------+
| i1   | c1   | i2   | c2   |
+------+------+------+------+
|    1 | a    | NULL | NULL |
|    2 | b    |    2 | c    |
|    3 | c    |    3 | b    |
+------+------+------+------+
現在有一個輸出,即使值1在表t2中並沒有匹配的值。

當你想要找到那些左表的行,而這些行又沒有在右表中被匹配到,那麼LEFT JOIN是非常有用的。你可以添加where子句來尋找在右表中值爲NULL的行,換句話說,該行在一個表中出現,但是在另一個表中沒有出現。

mysql> SELECT t1.*, t2.* FROM t1 LEFT JOIN t2 ON t1.i1 = t2.i2
  -> WHERE t2.i2 IS NULL;
+----+----+------+------+
| i1 | c1 | i2   | c2   |
+----+----+------+------+
| 1  | a  | NULL | NULL |
+----+----+------+------+
正常情況下,你真正關心的是左表中未匹配的值。展現在右表中的NULL列並不是你所感興趣的,所以你可以在輸出列中不顯示它們:

mysql> SELECT t1.* FROM t1 LEFT JOIN t2 ON t1.i1 = t2.i2
  -> WHERE t2.i2 IS NULL;
+----+----+
| i1 | c1 |
+----+----+
| 1  | a  |
+----+----+
LEFT JOIN實際上允許通過兩種方式指定匹配條件。On是其中一種;它可以使用在你要聯合的兩列是否有相同的名字:

SELECT t1.*, t2.* FROM t1 LEFT JOIN t2 ON t1.i1 = t2.i2;
另一種語法涉及到USING()子句;在概念上與ON是相似的,但是聯合的列的名字或者列必須有相同的名字在每一個表中。例如,以下的語句聯合mytbl1.b到mytbl2.b上:

SELECT mytbl1.*, mytbl2.* FROM mytbl1 LEFT JOIN mytbl2 USING (b);
LEFT JOIN有一些同義詞和變量。LEFT OUTER JOIN是一個LEFT JOIN的同義詞。也有一個被MySQL接受的ODBC-style的表示法(OJ意思是“outer join”):

{ OJ tbl_name1 LEFT OUTER JOIN tbl_name2 ON join_expr }
NATURAL LEFT JOIN和LEFT JOIN很相似;它執行一個LEFT JOIN,匹配所有在左表和右表中有相同的列。

對LEFT JOIN來說有一點需要注意的是,如果你要聯合的列沒有被聲明爲NOT NULL,你可能在結果中得到有問題的行。例如,如果右表中包含一些列中有NULL值,你將不能夠區分這些NULL與那些被標明爲未匹配的行導致的NULL。

正如我們上面提到的,LEFT JOIN對於回答”哪些值丟失了的問題”是很有用的。當你想要知道哪些值在一個表中存在但是在另一個表中不存在時,你使用LEFT JOIN在兩個表上並且尋找在第二個表中值爲NULL的行。讓我們考慮一個同樣類型比起前面使用的t1和t2更復雜的例子。
我們在第一章中提到了一個分數保持項目,我們有一個student表列出了學生,event表列出了已發生的班級事件,和一個score表列出了每個班級事件的每個學生的分數。但是,如果一個學生在某次測試或者考試時生病了,score表將不會顯示該學生該事件的分數值,所以該學生應該接受補考。我們怎樣才能夠得到這些缺失的記錄,從而使得我們可以確保這些學生得到一次補考?

問題是來決定對指定的班級事件哪些學生沒有分數。另一種方式說這個事情是,我們想要找出哪些學生和事件組合沒有在分數表中出現。這個“哪個值沒有出現”是一個我們需要LEFT JOIN的提示。這個join不和之前的例子那樣簡單,因爲我們並不是只尋找沒有出現在單一列中的值;我們要尋找的是兩個列的組合。我們想要的這個組合是所有的學生/事件組合,這需要通過跨student表和event表來產生:

FROM student, event

然後我們得到那個join的結果並且執行一個LEFT JOIN到score表來找到匹配值:

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表中。這些缺失的得分記錄的行可以通過從得分表中的列爲NULL來標識。我們可以選擇這些記錄在WHERE子句中。任何來自於score表中的列,因爲我們尋找的是缺失的分數,可能是概念上最清晰地測試score列:

WHERE score.score IS NULL
我們可以通過使用ORDER BY子句來對結果進行排序。兩個最具有邏輯的排序是通過每個student的event或者每個event的student。這裏我將選擇第一種:

ORDER BY student.student_id, event.event_id
現在我們需要做的事是對我們想要在輸出中看到的列進行命名。我們做好了,這是我們最終的查詢語句:

SELECT
   student.name, student.student_id,
   event.data, event.event_id, event.type
FROM
   student, event
   LEFT JOIN score ON student.student_id = score.student_id AND event.event_id = score.event_id
WHERE
   score.score IS NULL
ORDER BY
   student.student_id, event.event_id;

運行上面的查詢語句產生如下的結果:

+-----------+------------+------------+----------+------+
| name      | student_id | date       | event_id | type |
+-----------+------------+------------+----------+------+
| Megan     |     1      | 2002-09-16 |    4     | Q    |
| Joseph    |     2      | 2002-09-03 |    1     | Q    |
| Katie     |     4      | 2002-09-23 |    5     | Q    |
| Devri     |     13     | 2002-09-03 |    1     | Q    |
| Devri     |     13     | 2002-10-01 |    6     | T    |
| Will      |     17     | 2002-09-16 |    4     | Q    |
| Avery     |     20     | 2002-09-06 |    2     | Q    |
| Gregory   |     23     | 2002-10-01 |    6     | T    |
| Sarah     |     24     | 2002-09-23 |    5     | Q    |
| Carter    |     27     | 2002-09-16 |    4     | Q    |
| Carter    |     27     | 2002-09-23 |    5     | Q    |
| Gabrielle |     29     | 2002-09-16 |    4     | Q    |
| Grace     |     30     | 2002-09-23 |    5     | Q    |
+-----------+------------+------------+----------+------+

這裏有一個很巧妙的點。輸出顯示了student的IDs和event的IDs。student_id列既出現在student表中,也出現在score表中,所以第一眼,你可能會認爲選擇出的列會以student.student_id或者score.student_id命名。但結果並不是這樣,因爲對可能地找到我們感興趣的記錄的整個基礎是所有的score表中返回NULL的列。選擇score.student_id將在輸出中產生NULL值唯一的一列。相同的原則應用在決定哪一個event_id列會被顯示。它既在event表中出現,也在score表中出現,但是我們的查詢語句選擇event.event_id,因爲score.event_id值總是爲NULL。

使用子查詢

MySQL 4.1引入的其中一個功能是支持子查詢,也就是允許SELECT查詢語句具備嵌入到另一個查詢語句中。如下的例子是查找對應着tests('T')的event記錄的IDs,並且使用它們來選擇分數記錄:

SELECT * FROM score
WHERE event_id IN (SELECT event_id FROM event WHERE type = 'T');

在一些情況下,子查詢也可以重寫爲joins。我將在本章稍後介紹它。如果你的MySQL的版本小於4.1,你會發現子查詢從寫技術特別有用。

MySQL支持的一個相關的功能是你可以基於一個表中的內容來刪除或者更新另一個表中的記錄。例如,你可能想要刪除一個表中未在別的表中匹配的記錄,或者將一個表中的列拷貝到另一個表中。這些操作類型會在本章隨後的“多表刪除和更新”部分中談到。
這裏有一些寫子查詢的形式;這部分僅僅概述其中的一部分。

  • 使用一個子查詢來產生參考值。在這個例子中,你想要inner SELECT來標識一個單一的值用在於outer SELECT的比較上。例如,爲了標識發生在‘2002-09-23’號的測驗的分數,使用inner SELECT來決定測驗事件ID,然後與在outer SELECT匹配分數記錄:
  • SELECT * FROM score
    WHERE event_id = 
    (SELECT event_id FROM event WHERE date = '2002-09-23' AND type = 'Q');
  • 使用這種形式的子查詢,內部查詢先於比較運算符,inner join必須產生不能超過一個的單一值(那就是,一行或者一列)。如果它產生多個值,查詢就會失敗。(在一些情形下,可能需要通過LIMIT 1來限制內部查詢的結果來滿足這個條件)
  • 這種形式的子選擇對於你想要在WHERE子句中使用聚合函數來說是非常方便的。例如,想要決定哪一個總統最先出生,你可以嘗試如下語句:
  • SELECT * FROM president WHERE birth = MIN(birth);
  • 這並不會產生你想要的結果,因爲你不能在WHERE子句中使用聚合函數。(WHERE子句決定選擇哪一些記錄,但是MIN()的值並不知道,直到這些記錄已經被選擇之後)。但是你可以使用子查詢來產生最小的出生日期,如下所示:
  • SELECT * FROM president
    WHERE birth = (SELECT MIN(birth) FROM president);
  • EXISTS和NOT EXISTS子查詢。通過從外層查詢傳入內層來看是否滿足在內層查詢中的條件。因爲這個原因,如果它們是含混不清的(出現在不止一個表中)你需要用表名量化列名。EXISTS和NOT EXISTS子查詢對於找到在一個表中的記錄匹配或者不匹配另一個表中的記錄是很有用的。再一次引用一下我們的t1和t2表:
  • Table t1:   Table t2:
    +----+----+  +----+----+
    | i1 | c1 |  | i2 | c2 |
    +----+----+  +----+----+
    | 1  | a  |  | 2  | c  |
    | 2  | b  |  | 3  | b  |
    | 3  | c  |  | 4  | a  |
    +----+----+  +----+----+
  • 如下的查詢標明瞭在兩個表中的匹配--那就是,在兩個表中都出現的值:
  • mysql> SELECT i1 FROM t1
      -> WHERE EXISTS (SELECT * FROM t2 WHERE t1.i1 = t2.i2);
    +----+
    | i1 |
    +----+
    | 2  |
    | 3  |
    +----+
  • NOT EXISTS標明不匹配--只出現在一個表中,在另一個表中不存在:
  • mysql> SELECT i1 FROM t1
      -> WHERE NOT EXISTS (SELECT * FROM t2 WHERE t1.i1 = t2.i2);
    +----+
    | i1 |
    +----+
    | 1  |
    +----+
  • 通過這種形式的子查詢,內部查詢使用*作爲輸出列表列。沒有必要顯式地命名列,因爲內部查詢基於是否返回行來評估真或者假,並不基於那一行是否包含特定地值。在MySQL中,你可以對列的選擇列表寫任何事情,但是如果你想要當內部SELECT成功時顯式地你要返回一個真值,你可能需要像下面這樣寫查詢語句:
  • SELECT i1 FROM t1
    WHERE EXISTS (SELECT 1 FROM T2 WHERE t1.i1 = t2.i2);
    SELECT i1 FROM t1
    WHERE NOT EXISTS (SELECT 1 FROM t2 WHERE t1.i1 = t2.i2);
  • IN和NOT IN子查詢。IN和NOT IN形式的子查詢應該返回一個單一的列的值從內SELECT來在一個比較中評估。例如,之前的EXISTS和NOT EXISTS查詢可以通過使用IN和NOT IN寫爲如下這樣:
  • mysql> SELECT i1 FROM t1 WHERE i1 IN (SELECT i2 FROM t2);
    +----+
    | i1 |
    +----+
    | 2  |
    | 3  |
    +----+
    mysql> SELECT i1 FROM t1 WHERE i1 NOT IN (SELECT i2 FROM t2);
    +----+
    | i1 |
    +----+
    | 1  |
    +----+

像Join一樣重寫子查詢

對於MySQL 4.1之前的版本,是沒有子查詢的。但是,經常重寫一個查詢語句:以join的形式使用子查詢。實際上,即使你用的是4.1 後的MySQL,你可能傾向於以子查詢的方式來寫。join有時候比起子查詢來說效率更高。

重寫子查詢來選擇匹配的值

如下是包含子查詢的一個例子;它從分數表中選擇分數對所有的測試(那就是,它忽略了小測驗):

SELECT * FROM score WHERE event_id IN (SELECT event_id FROM event WHERE type = 'T');

通過將子查詢轉換爲join可以重寫出相同功能的語句:

SELECT score.* FROM score, event WHERE score.event_id = event.event_id AND event.type = 'T';

像另外一個例子,如下的查詢語句查詢女學生的分數:

SELECT * FROM score
WHERE student_id IN (SELECT student_id FROM student WHERE sex = 'F');

這也可以轉爲Join,如下所示:

SELECT score.* FROM score, student 
WHERE score.student_id = student.student_id AND student.sex = 'F';
這裏有一個模式。子查詢語句遵從如下的形式:

SELECT * FROM table1
WHERE column1 IN (SELECT column2a FROM table2 WHERE column2b = value);
這些查詢可以用如下的形式轉爲join:

SELECT table1.* FROM table1, table2
WHERE table1.column1 = table2.column2a AND table2.column2b = value;

重寫子查詢來選擇不匹配的值

子查詢的另一個常見的類型是查詢在一個表中出現,但在另一個表中不出現的值。正如我們之前看到的,“這些值不存在”的問題是一個使用LEFT JOIN的線索。如下是一個帶有子查詢的查詢語句,查詢不在absence表中的學生(找到出勤率很好的學生):

SELECT * FROM student
WHERE student_id NOT IN (SELECT student_id FROM absence);
這個查詢語句可以用一個LEFT JOIN重寫爲:

SELECT student.*
FROM student LEFT JOIN absence ON student.student_id = absence.student_id
WHERE absence.student_id IS NULL;
子查詢語句的通用形式如下:

SELECT * FROM table1
WHERE column1 NOT IN (SELECT column2 FROM table2);
用LEFT JOIN則可以重寫爲:

SELECT table1.*
FROM table1 LEFT JOIN table2 ON table1.column1 = table2.column2
WHERE table2.column2 IS NULL;
這裏table2.column2聲明爲非空。

從多個表中用UNION檢索

如果你想要通過從一個接一個地多個表中選擇記錄創建一個結果集,你可以使用一個UNION語句。UNION首次出現在MySQL4中,當然之前也有一些其它選項。

假設你有三個表,t1, t2, t3,它們看起來如下:

mysql> SELECT * FROM t1;
+------+-------+
| i    | c     |
+------+-------+
|  1   | red   |
|  2   | blue  |
|  3   | green |
+------+-------+
mysql> SELECT * FROM t2;
+------+------+
| i    | c    |
+------+------+
|  -1  | tan  |
|  1   | red  |
+------+------+
mysql> SELECT * FROM t3;
+------------+------+
| d          | i    |
+------------+------+
| 1904-01-01 | 100  |
| 2004-01-01 | 200  |
| 2004-01-01 | 200  |
+------------+------+

表一和表2含有整型值和字符串列,表3含有時間和整形列。用UNION語句來連接多個檢索,只要寫一些SELECT語句,並將UNION介於其中。例如,從每個表中選擇整形值的列,像如下:

mysql> SELECT i FROM t1 UNION SELECT i FROM t2 UNION SELECT i FROM t3;
+------+
| i    |
+------+
|  1   |
|  2   |
|  3   |
|  -1  |
| 100  |
| 200  |
+------+

UNION具有如下的屬性:

  • UNION結果的列的數據名和數據類型來自於第一個SELECT的數據名和數據類型。在UNION中的第二個SELECT和子查詢的SELECT語句必須選擇相同數量的列,但是它不需要有相同的名字或者類型。列由位置匹配,而不是名字匹配,這就是爲什麼這兩種返回不同結果的原因:
  • mysql> SELECT i, c FROM t1 UNION SELECT i, d FROM t3;
    +------+------------+
    | i  | c     |
    +------+------------+
    |  1 | red    |
    |  2 | blue    |
    |  3 | green   |
    | 100 | 1904-01-01 |
    | 200 | 2004-01-01 |
    +------+------------+
    mysql> SELECT i, c FROM t1 UNION SELECT d, i FROM t3;
    +------+-------+
    | i  | c   |
    +------+-------+
    |  1 | red  |
    |  2 | blue |
    |  3 | green |
    | 1904 | 100  |
    | 2004 | 200  |
    +------+-------+
  • 上面的兩個例子中,從表t1中選擇的列(i和c)決定了在UNION中使用的類型。這些列有整形和字符串型,所以當從表t3中選擇值時需要有類型轉換。對於第一個查詢,d從時間類型轉換爲字符串類型。這種情況沒有導致信息損失。但是對月第二個查詢來說,d從時間類型轉爲整形,這就帶來了信息的損失,而且i從整形轉換爲了字符串型。
  • 默認情況下,UNION會從結果集中刪除重複的行:
  • mysql> SELECT * FROM t1 UNION SELECT * FROM t2 UNION SELECT * FROM t3;
    +------+-------+
    | i    | c     |
    +------+-------+
    |  1   | red   |
    |  2   | blue  |
    |  3   | green |
    |  -1  | tan   |
    | 1904 | 100   |
    | 2004 | 200   |
    +------+-------+
  • t1和t2都有包含該值爲1和'red'的行,但是在輸出中只顯示了一行。同樣,t3有兩行包含'2014-01-01'和200,其中一行就被刪掉了。
  • 如果你想要保留重複,將第一個UNION關鍵字後加一個ALL:
  • mysql> SELECT * FROM t1 UNION ALL SELECT * FROM t2 UNION SELECT * FROM t3;
    +------+-------+
    | i    | c     |
    +------+-------+
    |  1   | red   |
    |  2   | blue  |
    |  3   | green |
    |  -1  | tan   |
    |  1   | red   |
    | 1904 | 100   |
    | 2004 | 200   |
    | 2004 | 200   |
    +------+-------+
  • 爲了對UNION的結果進行排序,在最後一個SELECT後加上ORDER BY語句;它將整個查詢語句結果作爲一個整體來看待。但是,因爲UNION使用來自於第一個SELECT的列名爲其最終的列名,ORDER BY應該指的是這些名字,而不是從最後一個SELECT中得到的名字,如果它們有不同的話。
  • mysql> SELECT i, c FROM t1 UNION SELECT i, d FROM t3
      -> ORDER BY c;
    +------+------------+
    | i    | c          |
    +------+------------+
    | 100  | 1904-01-01 |
    | 200  | 2004-01-01 |
    |  2   | blue       |
    |  3   | green      |
    |  1   | red        |
    +------+------------+
  • 你也可以對在UNION中單獨的SELECT語句指定ORDER BY語句。要這麼做,只需要將SELECT(包括ORDER BY)語句用小括號括起來即可:
  • mysql> (SELECT i, c FROM t1 ORDER BY i DESC)
      -> UNION (SELECT i, c FROM t2 ORDER BY i);
    +------+-------+
    | i    | c     |
    +------+-------+
    |  3   | green |
    |  2   | blue  |
    |  1   | red   |
    |  -1  | tan   |
    +------+-------+
  • LIMIT也可以作用在UNION上,類似於在ORDER BY上的應用。如果將其加在語句的最後,它將對UNION的結果作爲一個整體來使用:
  • mysql> SELECT * FROM t1 UNION SELECT * FROM t2 UNION SELECT * FROM t3
      -> LIMIT 1;
    +------+------+
    | i    | c    |
    +------+------+
    |  1   | red  |
    +------+------+
  • 如果將LIMIT在每一個SELECT中括起來使用,它只應用在那一個SELECT上:
  • mysql> (SELECT * FROM t1 LIMIT 1)
      -> UNION (SELECT * FROM t2 LIMIT 1)
      -> UNION (SELECT * FROM t3 LIMIT 1);
    +------+------+
    | i    | c    |
    +------+------+
    |  1   | red  |
    |  -1  | tan  |
    | 1904 | 100  |
    +------+------+
  • 你不需要從不同的表中來選擇。你可以使用不同的條件來選擇相同表的不同的子集。這對於作爲一個替代的來運行不同的SELECT查詢來說是很有用的,因爲你從一個單一的結果集中得到了所有行,而不是多個結果集中。
在早於MySQL 4的版本前,是沒有UNION的,但是有一個比較困難的解決方法是通過從每一個表中選擇行,並將其放到一個臨時表中,最後對這個臨時表的內容進行選擇。在MySQL 3.23以及以後的版本中,你可以簡單地處理這些問題通過允許服務器來爲你創建holding表。當然,你也可以將這個表設置爲臨時表,當在這個服務器中的session終止時,它會自動地刪除這個表。爲了更高的效率,使用一個HEAP(in memory)表。

CREATE TEMPORARY TABLE tmp TYPE = HEAP SELECT ... FROM T1 WHERE ...;
INSERT INTO tmp SELECT ... FROM t2 WHERE ... ;
INSERT INTO tmp SELECT ... FROM t3 WHERE ... ;
...
SELECT * FROM tmp ORDER BY ... ;
因爲tmp是一個臨時表,但你的客戶端終止時,服務器會自動將該表刪除。(當然,你也可以顯式地刪除該表,只要你完成了工作,使得服務器釋放與之有關的資源。如果你要持續地執行進一步地查詢,尤其是對HEAP表,這是一個很好的主意。)

對於早於3.23的MySQL版本,概念是相似的,但是細節上有較大的差別,因爲那時是不存在HEAP表類型以及臨時表的,就是CREATE TABLE ... SELECT。爲了適應上述的進程,首先在檢索任何行到其中之前必須顯式地創建一個表。(唯一支持的的表類型是ISAM,所以你不能使用TYPE選項)然後將記錄檢索到表中。當你完成了之後,你必須顯式地使用DROP TABLE來刪除表,因爲服務器不會自動來刪除一個表。

CREATE TABLE tmp (column1, column2, ...);
INSERT INTO tmp SELECT ... FROM t1 WHERE ... ;
INSERT INTO tmp SELECT ... FROM t2 WHERE ... ;
INSERT INTO tmp SELECT ... FROM t3 WHERE ... ;
SELECT * FROM tmp ORDER BY ... ;
DROP TABLE tmp;


本文翻譯自這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章