Author: ACatSmiling
Since: 2024-09-19
多表查詢
多表查詢
,也稱為關聯查詢
,指兩個或更多個表一起完成查詢操作。
前提條件:這些一起查詢的表之間是有關係的(一對一、一對多),它們之間一定是有關聯欄位,這個關聯欄位可能建立了外來鍵,也可能沒有建立外來鍵。比如:員工表和部門表,這兩個表依靠 "部門編號" 進行關聯。
笛卡爾積
笛卡爾乘積
是一個數學運算。假設有兩個集合 X 和 Y,那麼 X 和 Y 的笛卡爾積就是 X 和 Y 的所有可能組合,也就是第一個物件來自於 X,第二個物件來自於 Y 的所有可能。組合的個數即為兩個集合中元素個數的乘積數。
SQL-92 中,笛卡爾積也稱為交叉連線 ,英文是 CROSS JOIN 。在 SQL-99 中也是使用 CROSS JOIN 表示交叉連線,它的作用就是可以把任意表進行連線,即使這兩張表不相關。在 MySQL 中如下情況會出現笛卡爾積:
# 查詢員工姓名和所在部門名稱
mysql> SELECT last_name, department_name FROM employees, departments;
+-------------+----------------------+
| last_name | department_name |
+-------------+----------------------+
| King | Payroll |
| King | Recruiting |
| King | Retail Sales |
| King | Government Sales |
| King | IT Helpdesk |
| King | NOC |
| King | IT Support |
| King | Operations |
| King | Contracting |
| King | Construction |
| King | Manufacturing |
| King | Benefits |
| King | Shareholder Services |
| King | Control And Credit |
| King | Corporate Tax |
| King | Treasury |
| King | Accounting |
| King | Finance |
| King | Executive |
| King | Sales |
| King | Public Relations |
| King | IT |
| King | Shipping |
| King | Human Resources |
| King | Purchasing |
| King | Marketing |
| King | Administration |
| Kochhar | Payroll |
| ...... |
+-------------+----------------------+
2889 rows in set (0.00 sec)
mysql> SELECT last_name, department_name FROM employees CROSS JOIN departments;
+-------------+----------------------+
| last_name | department_name |
+-------------+----------------------+
| King | Payroll |
| King | Recruiting |
| King | Retail Sales |
| King | Government Sales |
| King | IT Helpdesk |
| King | NOC |
| King | IT Support |
| King | Operations |
| King | Contracting |
| King | Construction |
| King | Manufacturing |
| King | Benefits |
| King | Shareholder Services |
| King | Control And Credit |
| King | Corporate Tax |
| King | Treasury |
| King | Accounting |
| King | Finance |
| King | Executive |
| King | Sales |
| King | Public Relations |
| King | IT |
| King | Shipping |
| King | Human Resources |
| King | Purchasing |
| King | Marketing |
| King | Administration |
| Kochhar | Payroll |
| ...... |
+-------------+----------------------+
2889 rows in set (0.00 sec)
mysql> SELECT last_name, department_name FROM employees INNER JOIN departments;
+-------------+----------------------+
| last_name | department_name |
+-------------+----------------------+
| King | Payroll |
| King | Recruiting |
| King | Retail Sales |
| King | Government Sales |
| King | IT Helpdesk |
| King | NOC |
| King | IT Support |
| King | Operations |
| King | Contracting |
| King | Construction |
| King | Manufacturing |
| King | Benefits |
| King | Shareholder Services |
| King | Control And Credit |
| King | Corporate Tax |
| King | Treasury |
| King | Accounting |
| King | Finance |
| King | Executive |
| King | Sales |
| King | Public Relations |
| King | IT |
| King | Shipping |
| King | Human Resources |
| King | Purchasing |
| King | Marketing |
| King | Administration |
| Kochhar | Payroll |
| ...... |
+-------------+----------------------+
2889 rows in set (0.00 sec)
mysql> SELECT last_name, department_name FROM employees JOIN departments;
+-------------+----------------------+
| last_name | department_name |
+-------------+----------------------+
| King | Payroll |
| King | Recruiting |
| King | Retail Sales |
| King | Government Sales |
| King | IT Helpdesk |
| King | NOC |
| King | IT Support |
| King | Operations |
| King | Contracting |
| King | Construction |
| King | Manufacturing |
| King | Benefits |
| King | Shareholder Services |
| King | Control And Credit |
| King | Corporate Tax |
| King | Treasury |
| King | Accounting |
| King | Finance |
| King | Executive |
| King | Sales |
| King | Public Relations |
| King | IT |
| King | Shipping |
| King | Human Resources |
| King | Purchasing |
| King | Marketing |
| King | Administration |
| Kochhar | Payroll |
| ...... |
+-------------+----------------------+
2889 rows in set (0.00 sec)
上述查詢結果,稱為
笛卡爾積的錯誤
,原因:mysql> SELECT COUNT(employee_id) FROM employees; +--------------------+ | COUNT(employee_id) | +--------------------+ | 107 | +--------------------+ 1 row in set (0.00 sec) mysql> SELECT COUNT(department_id) FROM departments; +----------------------+ | COUNT(department_id) | +----------------------+ | 27 | +----------------------+ 1 row in set (0.00 sec) mysql> SELECT 107 * 27 FROM dual; +----------+ | 107 * 27 | +----------+ | 2889 | +----------+ 1 row in set (0.00 sec)
笛卡爾積的錯誤會在下面條件下產生:
- 省略多個表的連線條件(或關聯條件)。
- 連線條件(或關聯條件)無效。
- 所有表中的所有行互相連線。
為了避免笛卡爾積, 可以在 WHERE 加入有效的連線條件。加入連線條件後,查詢語法:
SELECT table1.column, table2.column
FROM table1, table2
WHERE table1.column1 = table2.column2; # 連線條件
連線 n 個表,至少需要 n - 1 個連線條件。
查詢員工的姓名及其部門名稱,正確的寫法:
mysql> SELECT last_name, department_name FROM employees, departments WHERE employees.department_id = departments.department_id;
+-------------+------------------+
| last_name | department_name |
+-------------+------------------+
| Whalen | Administration |
| Hartstein | Marketing |
| Fay | Marketing |
| Raphaely | Purchasing |
+-------------+------------------+
106 rows in set (0.00 sec)
在不同表中有相同欄位時,需要在欄位前加上所屬表名的字首。多表查詢時,建議對每一個查詢欄位,都新增上所屬表名的字首,這樣一是避免相同欄位不明確,二是可以對查詢進行最佳化,以提高查詢效率。
需要注意的是,如果使用了表的別名,在查詢欄位中、過濾條件中就只能使用別名進行代替,不能使用原有的表名,否則就會報錯,這涉及到 SQL 語句不同分段的順序執行問題。
阿里開發規範 :
【 強制 】對於資料庫中表記錄的查詢和變更,只要涉及多個表,都需要在列名前加表的別名(或表名)進行限定。說明 :對多表進行查詢記錄、更新記錄、刪除記錄時,如果對操作列沒有限定表的別名(或表名),並且操作列在多個表中存在時,就會拋異常。
正例 :SELECT t1.name FROM table_first AS t1, table_second AS t2 WHERE t1.id = t2.id;
反例 :在某業務中,由於多表關聯查詢語句沒有加表的別名(或表名)的限制,正常執行兩年後,最近在某個表中增加一個同名欄位,在預釋出環境做資料庫變更後,線上查詢語句出現出 1052 異常:Column 'name' in field list is ambiguous。
多表查詢的分類
分類 1:等值連線 vs 非等值連線
等值連線
mysql> SELECT employees.employee_id, employees.last_name, employees.department_id, departments.department_id, departments.location_id FROM employees, departments WHERE employees.department_id = departments.department_id;
+-------------+-------------+---------------+---------------+-------------+
| employee_id | last_name | department_id | department_id | location_id |
+-------------+-------------+---------------+---------------+-------------+
| 103 | Hunold | 60 | 60 | 1400 |
| 104 | Ernst | 60 | 60 | 1400 |
| 105 | Austin | 60 | 60 | 1400 |
| 106 | Pataballa | 60 | 60 | 1400 |
| 107 | Lorentz | 60 | 60 | 1400 |
+-------------+-------------+---------------+---------------+-------------+
106 rows in set (0.00 sec)
非等值連線
mysql> SELECT e.last_name, e.salary, j.grade_level FROM employees e, job_grades j WHERE e.salary BETWEEN j.lowest_sal AND j.highest_sal;
+-------------+----------+-------------+
| last_name | salary | grade_level |
+-------------+----------+-------------+
| King | 24000.00 | E |
| Kochhar | 17000.00 | E |
| De Haan | 17000.00 | E |
| Hunold | 9000.00 | C |
| Ernst | 6000.00 | C |
| Austin | 4800.00 | B |
| Pataballa | 4800.00 | B |
| Lorentz | 4200.00 | B |
| Greenberg | 12000.00 | D |
| Gietz | 8300.00 | C |
+-------------+----------+-------------+
107 rows in set (0.00 sec)
分類 2:自連線 vs 非自連線
table1 和 table2 本質上是同一張表,只是用取別名的方式虛擬成兩張表以代表不同的意義,然後兩個表再進行內連線、外連線等查詢。
mysql> SELECT CONCAT(worker.last_name, ' works for ', manager.last_name) FROM employees worker, employees manager WHERE worker.manager_id = manager.employee_id;
+------------------------------------------------------------+
| CONCAT(worker.last_name, ' works for ', manager.last_name) |
+------------------------------------------------------------+
| Kochhar works for King |
| De Haan works for King |
| Hunold works for De Haan |
| Ernst works for Hunold |
| Austin works for Hunold |
| Pataballa works for Hunold |
| Lorentz works for Hunold |
| Gietz works for Higgins |
+------------------------------------------------------------+
106 rows in set (0.00 sec)
分類 3:內連線 vs 外連線
內連線
:合併具有同一列的兩個以上的表的行,結果集中不包含一個表與另一個表不匹配的行。
外連線
:兩個表在連線過程中除了返回滿足連線條件的行以外,還返回左(或右)表中不滿足條件的行 ,這種連線稱為左(或右) 外連線。沒有匹配的行時,結果表中相應的列為空(NULL)。
-
如果是左外連線,則連線條件中左邊的表也稱為
主表
,右邊的表稱為從表
。 -
如果是右外連線,則連線條件中右邊的表也稱為
主表
,左邊的表稱為從表
。
SQL-99 語法實現多表查詢
基本語法
使用JOIN...ON
子句建立連線的語法結構:
SELECT table1.column, table2.column,table3.column
FROM table1
JOIN table2 ON table1 和 table2 的連線條件
JOIN table3 ON table2 和 table3 的連線條件;
語法說明:
- 可以使用 ON 子句指定額外的連線條件。
- 這個連線條件是與其它條件分開的。
- ON 子句使語句具有更高的易讀性。
- 關鍵字 JOIN、INNER JOIN、CROSS JOIN 的含義是一樣的,都表示內連線。
它的巢狀邏輯類似我們使用的 FOR 迴圈:
for t1 in table1:
for t2 in table2:
if condition1:
for t3 in table3:
if condition2:
output t1 + t2 + t3
SQL-99 採用的這種巢狀結構非常清爽、層次性更強、可讀性更強,即使再多的表進行連線也都清晰可見。如果採用 SQL-92,可讀性就會大打折扣。
內連線(INNER JOIN)的實現
語法:
SELECT 欄位列表
FROM A表 INNER JOIN B表
ON 關聯條件
WHERE 等其他子句;
mysql> SELECT e.employee_id, e.last_name, e.department_id, d.department_id, d.location_id FROM employees e JOIN departments d ON (e.department_id = d.department_id);
+-------------+-------------+---------------+---------------+-------------+
| employee_id | last_name | department_id | department_id | location_id |
+-------------+-------------+---------------+---------------+-------------+
| 103 | Hunold | 60 | 60 | 1400 |
| 104 | Ernst | 60 | 60 | 1400 |
| 105 | Austin | 60 | 60 | 1400 |
| 106 | Pataballa | 60 | 60 | 1400 |
| 107 | Lorentz | 60 | 60 | 1400 |
| 204 | Baer | 70 | 70 | 2700 |
+-------------+-------------+---------------+---------------+-------------+
106 rows in set (0.00 sec)
mysql> SELECT employee_id, city, department_name FROM employees e JOIN departments d ON d.department_id = e.department_id JOIN locations l ON d.location_id = l.location_id;
+-------------+---------------------+------------------+
| employee_id | city | department_name |
+-------------+---------------------+------------------+
| 200 | Seattle | Administration |
| 201 | Toronto | Marketing |
| 202 | Toronto | Marketing |
| 114 | Seattle | Purchasing |
| 115 | Seattle | Purchasing |
| 116 | Seattle | Purchasing |
| 117 | Seattle | Purchasing |
| 118 | Seattle | Purchasing |
| 119 | Seattle | Purchasing |
| 203 | London | Human Resources |
| 120 | South San Francisco | Shipping |
| 206 | Seattle | Accounting |
+-------------+---------------------+------------------+
106 rows in set (0.00 sec)
外連線(OUTER JOIN)的實現
左外連線(LEFT OUTER JOIN)
語法:
# 實現查詢結果是 A
SELECT 欄位列表
FROM A表 LEFT JOIN B表
ON 關聯條件
WHERE 等其他子句;
mysql> SELECT e.last_name, e.department_id, d.department_name FROM employees e LEFT OUTER JOIN departments d ON (e.department_id = d.department_id);
+-------------+---------------+------------------+
| last_name | department_id | department_name |
+-------------+---------------+------------------+
| King | 90 | Executive |
| Kochhar | 90 | Executive |
| De Haan | 90 | Executive |
| Hunold | 60 | IT |
| Ernst | 60 | IT |
| Livingston | 80 | Sales |
| Grant | NULL | NULL |
| Johnson | 80 | Sales |
| Gietz | 110 | Accounting |
+-------------+---------------+------------------+
107 rows in set (0.00 sec)
內連線查詢結果是 106 條記錄,左外連線是 107 條記錄,有一個員工不存在部門資訊,內連線的時候,不會顯示這個員工的資訊。
右外連線(RIGHT OUTER JOIN)
語法:
# 實現查詢結果是 B
SELECT 欄位列表
FROM A表 RIGHT JOIN B表
ON 關聯條件
WHERE 等其他子句;
mysql> SELECT e.last_name, e.department_id, d.department_name FROM employees e RIGHT OUTER JOIN departments d ON (e.department_id = d.department_id);
+-------------+---------------+----------------------+
| last_name | department_id | department_name |
+-------------+---------------+----------------------+
| Whalen | 10 | Administration |
| Hartstein | 20 | Marketing |
| Fay | 20 | Marketing |
| Raphaely | 30 | Purchasing |
| Khoo | 30 | Purchasing |
| Baida | 30 | Purchasing |
| Tobias | 30 | Purchasing |
| Himuro | 30 | Purchasing |
| NULL | NULL | IT Support |
| NULL | NULL | NOC |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+---------------+----------------------+
122 rows in set (0.00 sec)
右連線查詢時,有些部門沒有員工,也會顯示出部門的資訊。
需要注意的是,LEFT JOIN 和 RIGHT JOIN 只存在於 SQL-99 及以後的標準中,在 SQL-92 中不存在,只能用(+)表示。
SQL-92 中採用(+)代表從表所在的位置。即左或右外連線中,(+)表示哪個是從表。Oracle 對 SQL-92 支援較好,而 MySQL 則不支援 SQL-92 的外連線。
# 左外連線 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id = departments.department_id(+); # 右外連線 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id(+) = departments.department_id;
滿外連線(FULL OUTER JOIN)
滿外連線的結果 = 左右表匹配的資料 + 左表沒有匹配到的資料 + 右表沒有匹配到的資料。
- SQL-99 是支援滿外連線的。使用 FULL JOIN 或 FULL OUTER JOIN 來實現。
- 需要注意的是:MySQL 不支援 FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT JOIN 代替。
在 SQL-92 中,只有左外連線和右外連線,沒有滿(或全)外連線。
合併查詢結果
利用 UNION 關鍵字可以合併查詢結果,可以給出多條 SELECT 語句,並將它們的結果組合成單個結果集。合併時,兩個表對應的列數和資料型別必須相同,並且相互對應。各個 SELECT 語句之間使用 UNION 或 UNION ALL 關鍵字分隔。
語法格式:
SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2;
UNION 運算子
UNION
運算子返回兩個查詢的結果集的並集,並去除重複記錄。
UNION ALL 運算子
UNION ALL
運算子返回兩個查詢的結果集的並集,對於兩個結果集的重複部分,不去重。
執行 UNION ALL 語句時所需要的資源比 UNION 語句少。如果明確知道合併資料後的結果資料不存在重複資料,或者不需要去除重複的資料,則儘量使用 UNION ALL 語句,以提高資料查詢的效率。
舉例,查詢郵箱包含 a 或者部門編號大於 90 的員工資訊:
mysql> SELECT * FROM employees WHERE email LIKE '%a%' OR department_id > 90;
+-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+
| employee_id | first_name | last_name | email | phone_number | hire_date | job_id | salary | commission_pct | manager_id | department_id |
+-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+
| 101 | Neena | Kochhar | NKOCHHAR | 515.123.4568 | 1989-09-21 | AD_VP | 17000.00 | NULL | 100 | 90 |
| 102 | Lex | De Haan | LDEHAAN | 515.123.4569 | 1993-01-13 | AD_VP | 17000.00 | NULL | 100 | 90 |
| 206 | William | Gietz | WGIETZ | 515.123.8181 | 1994-06-07 | AC_ACCOUNT | 8300.00 | NULL | 205 | 110 |
+-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+
67 rows in set (0.00 sec)
使用 UNION 寫法:
mysql> SELECT * FROM employees WHERE email LIKE '%a%' UNION SELECT * FROM employees WHERE department_id > 90;
+-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+
| employee_id | first_name | last_name | email | phone_number | hire_date | job_id | salary | commission_pct | manager_id | department_id |
+-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+
| 101 | Neena | Kochhar | NKOCHHAR | 515.123.4568 | 1989-09-21 | AD_VP | 17000.00 | NULL | 100 | 90 |
| 102 | Lex | De Haan | LDEHAAN | 515.123.4569 | 1993-01-13 | AD_VP | 17000.00 | NULL | 100 | 90 |
| 206 | William | Gietz | WGIETZ | 515.123.8181 | 1994-06-07 | AC_ACCOUNT | 8300.00 | NULL | 205 | 110 |
+-------------+-------------+------------+----------+--------------------+------------+------------+----------+----------------+------------+---------------+
67 rows in set (0.00 sec)
7 種 SQL JOINS 的實現
中圖:內連線 A ∩ B。
mysql> SELECT employee_id, last_name, department_name FROM employees e JOIN departments d ON e.department_id = d.department_id;
+-------------+-------------+------------------+
| employee_id | last_name | department_name |
+-------------+-------------+------------------+
| 200 | Whalen | Administration |
| 201 | Hartstein | Marketing |
| 202 | Fay | Marketing |
| 205 | Higgins | Accounting |
| 206 | Gietz | Accounting |
+-------------+-------------+------------------+
106 rows in set (0.00 sec)
左上圖:左外連線。
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id;
+-------------+-------------+------------------+
| employee_id | last_name | department_name |
+-------------+-------------+------------------+
| 100 | King | Executive |
| 101 | Kochhar | Executive |
| 102 | De Haan | Executive |
| 178 | Grant | NULL |
| 205 | Higgins | Accounting |
| 206 | Gietz | Accounting |
+-------------+-------------+------------------+
107 rows in set (0.00 sec)
右上圖:右外連線。
mysql> SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.`department_id`;
+-------------+-------------+----------------------+
| employee_id | last_name | department_name |
+-------------+-------------+----------------------+
| 200 | Whalen | Administration |
| 201 | Hartstein | Marketing |
| 202 | Fay | Marketing |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+-------------+----------------------+
122 rows in set (0.00 sec)
左中圖:A - A ∩ B。
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL;
+-------------+-----------+-----------------+
| employee_id | last_name | department_name |
+-------------+-----------+-----------------+
| 178 | Grant | NULL |
+-------------+-----------+-----------------+
1 row in set (0.00 sec)
右中圖:B - A ∩ B。
mysql> SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL;
+-------------+-----------+----------------------+
| employee_id | last_name | department_name |
+-------------+-----------+----------------------+
| NULL | NULL | Treasury |
| NULL | NULL | Corporate Tax |
| NULL | NULL | Control And Credit |
| NULL | NULL | Shareholder Services |
| NULL | NULL | Benefits |
| NULL | NULL | Manufacturing |
| NULL | NULL | Construction |
| NULL | NULL | Contracting |
| NULL | NULL | Operations |
| NULL | NULL | IT Support |
| NULL | NULL | NOC |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+-----------+----------------------+
16 rows in set (0.00 sec)
左下圖:滿外連線,左中圖 + 右上圖,A∪B。
# 沒有去重操作,效率高
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL UNION ALL SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id;
+-------------+-------------+----------------------+
| employee_id | last_name | department_name |
+-------------+-------------+----------------------+
| 178 | Grant | NULL |
| 200 | Whalen | Administration |
| 201 | Hartstein | Marketing |
| 202 | Fay | Marketing |
| 114 | Raphaely | Purchasing |
| NULL | NULL | Construction |
| NULL | NULL | Contracting |
| NULL | NULL | Operations |
| NULL | NULL | IT Support |
| NULL | NULL | NOC |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+-------------+----------------------+
123 rows in set (0.00 sec)
右下圖:左中圖 + 右中圖,A ∪B - A ∩ B,或者 (A - A ∩ B) ∪ (B - A ∩ B)。
mysql> SELECT employee_id, last_name, department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL UNION ALL SELECT employee_id, last_name, department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL;
+-------------+-----------+----------------------+
| employee_id | last_name | department_name |
+-------------+-----------+----------------------+
| 178 | Grant | NULL |
| NULL | NULL | Treasury |
| NULL | NULL | Corporate Tax |
| NULL | NULL | Control And Credit |
| NULL | NULL | Shareholder Services |
| NULL | NULL | Benefits |
| NULL | NULL | Manufacturing |
| NULL | NULL | Construction |
| NULL | NULL | Contracting |
| NULL | NULL | Operations |
| NULL | NULL | IT Support |
| NULL | NULL | NOC |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+-----------+----------------------+
17 rows in set (0.00 sec)
左中圖語法格式:
# 實現 A - A ∩ B
SELECT 欄位列表
FROM A表 LEFT JOIN B表
ON 關聯條件
WHERE 從表關聯欄位 IS NULL AND 等其他子句;
右中圖語法格式:
# 實現 B - A ∩ B
SELECT 欄位列表
FROM A表 RIGHT JOIN B表
ON 關聯條件
WHERE 從表關聯欄位 IS NULL AND 等其他子句;
左下圖語法格式:
# 實現查詢結果是 A ∪ B,用左外的 A,union 右外的 B
SELECT 欄位列表
FROM A表 LEFT JOIN B表
ON 關聯條件
WHERE 等其他子句
UNION
SELECT 欄位列表
FROM A表 RIGHT JOIN B表
ON 關聯條件
WHERE 等其他子句;
SQL-99 語法新特性
自然連線
SQL-99 在 SQL-92 的基礎上提供了一些特殊語法,比如NATURAL JOIN
用來表示自然連線。我們可以把自然連線理解為 SQL-92 中的等值連線,它會自動查詢兩張連線表中所有相同的欄位,然後進行等值連線。
在 SQL-92 標準中:
mysql> SELECT employee_id, last_name, department_name FROM employees e JOIN departments d ON e.department_id = d.department_id AND e.manager_id = d.manager_id;
+-------------+------------+-----------------+
| employee_id | last_name | department_name |
+-------------+------------+-----------------+
| 202 | Fay | Marketing |
| 115 | Khoo | Purchasing |
| 116 | Baida | Purchasing |
| 117 | Tobias | Purchasing |
| 118 | Himuro | Purchasing |
| 119 | Colmenares | Purchasing |
| 129 | Bissot | Shipping |
| 130 | Atkinson | Shipping |
| 131 | Marlow | Shipping |
| 132 | Olson | Shipping |
| 184 | Sarchand | Shipping |
| 185 | Bull | Shipping |
| 186 | Dellinger | Shipping |
| 187 | Cabrio | Shipping |
| 104 | Ernst | IT |
| 105 | Austin | IT |
| 106 | Pataballa | IT |
| 107 | Lorentz | IT |
| 150 | Tucker | Sales |
| 151 | Bernstein | Sales |
| 152 | Hall | Sales |
| 153 | Olsen | Sales |
| 154 | Cambrault | Sales |
| 155 | Tuvault | Sales |
| 101 | Kochhar | Executive |
| 102 | De Haan | Executive |
| 109 | Faviet | Finance |
| 110 | Chen | Finance |
| 111 | Sciarra | Finance |
| 112 | Urman | Finance |
| 113 | Popp | Finance |
| 206 | Gietz | Accounting |
+-------------+------------+-----------------+
32 rows in set (0.00 sec)
在 SQL-99 中可以寫成:
mysql> SELECT employee_id, last_name, department_name FROM employees e NATURAL JOIN departments d;
+-------------+------------+-----------------+
| employee_id | last_name | department_name |
+-------------+------------+-----------------+
| 202 | Fay | Marketing |
| 115 | Khoo | Purchasing |
| 116 | Baida | Purchasing |
| 117 | Tobias | Purchasing |
| 118 | Himuro | Purchasing |
| 119 | Colmenares | Purchasing |
| 129 | Bissot | Shipping |
| 130 | Atkinson | Shipping |
| 131 | Marlow | Shipping |
| 132 | Olson | Shipping |
| 184 | Sarchand | Shipping |
| 185 | Bull | Shipping |
| 186 | Dellinger | Shipping |
| 187 | Cabrio | Shipping |
| 104 | Ernst | IT |
| 105 | Austin | IT |
| 106 | Pataballa | IT |
| 107 | Lorentz | IT |
| 150 | Tucker | Sales |
| 151 | Bernstein | Sales |
| 152 | Hall | Sales |
| 153 | Olsen | Sales |
| 154 | Cambrault | Sales |
| 155 | Tuvault | Sales |
| 101 | Kochhar | Executive |
| 102 | De Haan | Executive |
| 109 | Faviet | Finance |
| 110 | Chen | Finance |
| 111 | Sciarra | Finance |
| 112 | Urman | Finance |
| 113 | Popp | Finance |
| 206 | Gietz | Accounting |
+-------------+------------+-----------------+
32 rows in set (0.00 sec)
USING 連線
當進行連線的時候,SQL-99 還支援使用USING
指定資料表裡的同名欄位進行等值連線,但是隻能配合 JOIN 一起使用。比如:
mysql> SELECT employee_id, last_name, department_name FROM employees e JOIN departments d USING (department_id);
+-------------+-------------+------------------+
| employee_id | last_name | department_name |
+-------------+-------------+------------------+
| 200 | Whalen | Administration |
| 201 | Hartstein | Marketing |
| 202 | Fay | Marketing |
| 205 | Higgins | Accounting |
| 206 | Gietz | Accounting |
+-------------+-------------+------------------+
106 rows in set (0.00 sec)
與自然連線 NATURAL JOIN 不同的是,USING 指定了具體的相同的欄位名稱,需要在 USING 的括號 () 中填入要指定的同名欄位。同時使用 JOIN...USING 可以簡化 JOIN ON 的等值連線。它與下面的 SQL 查詢結果是相同的:
mysql> SELECT employee_id, last_name, department_name FROM employees e, departments d WHERE e.department_id = d.department_id;
小結
表連線的約束條件可以有三種方式:WHERE,ON,USING。
- WHERE:適用於所有關聯查詢。
- ON :只能和 JOIN 一起使用,只能寫關聯條件。雖然關聯條件可以併到 WHERE 中和其他條件一起寫,但分開寫可讀性更好。
- USING:只能和 JOIN 一起使用,要求兩個關聯欄位在關聯表中名稱一致,而且只能表示關聯欄位值相等。
# 把關聯條件寫在 where 後面
mysql> SELECT last_name, department_name FROM employees, departments WHERE employees.department_id = departments.department_id;
# 把關聯條件寫在 on 後面,只能和 JOIN 一起使用
mysql> SELECT last_name, department_name FROM employees INNER JOIN departments ON employees.department_id = departments.department_id;
mysql> SELECT last_name, department_name FROM employees CROSS JOIN departments ON employees.department_id = departments.department_id;
mysql> SELECT last_name, department_name FROM employees JOIN departments ON employees.department_id = departments.department_id;
# 把關聯欄位寫在 using() 中,只能和 JOIN 一起使用,而且兩個表中的關聯欄位必須名稱相同,而且只能表示 =
# 查詢員工姓名與基本工資
mysql> SELECT last_name, job_title FROM employees INNER JOIN jobs USING(job_id);
# n 張表關聯,至少需要 n-1 個關聯條件
# 查詢員工姓名,基本工資,部門名稱
mysql> SELECT last_name, job_title, department_name FROM employees, departments, jobs WHERE employees.department_id = departments.department_id AND employees.job_id = jobs.job_id;
mysql> SELECT last_name, job_title, department_name FROM employees INNER JOIN departments INNER JOIN jobs ON employees.department_id = departments.department_id AND employees.job_id = jobs.job_id;
注意:
要控制連線表的數量
。多表連線就相當於巢狀 for 迴圈一樣,非常消耗資源,會讓 SQL 查詢效能下降得很嚴重,因此不要連線不必要的表。在許多 DBMS 中,也都會有最大連線表的限#制。
連表查詢的條件問題
正確寫法:
SELECT
ledger.credit_code AS '統一社會信用程式碼', info.company_name AS '企業名稱', ledger.hazard_ledger_id AS '主鍵',
(CASE ledger.danger_level WHEN 1 THEN '一級' WHEN 2 THEN '二級' WHEN 3 THEN '三級' WHEN 4 THEN '四級' ELSE ledger.danger_level END) AS '隱患等級',
(CASE ledger.manager_level WHEN 0 THEN '集團級' WHEN 1 THEN '公司級' WHEN 2 THEN '企業級' WHEN 3 THEN '車間級' WHEN 4 THEN '裝置級' ELSE ledger.manager_level END) AS '管理級別',
ledger.danger_type AS '隱患型別分類編碼',
(CASE
WHEN ledger.danger_type LIKE '1-%' THEN '健康'
WHEN ledger.danger_type LIKE '2-%' THEN '安全'
WHEN ledger.danger_type LIKE '3-%' THEN '環保'
WHEN ledger.danger_type LIKE '4-%' THEN '節能'
WHEN ledger.danger_type LIKE '5-%' THEN '低碳'
WHEN ledger.danger_type LIKE '6-%' THEN '安保'
ELSE ledger.danger_type
END) AS '隱患型別一級分類',
-- ledger.danger_name AS '隱患名稱', ledger.danger_description AS '隱患描述', ledger.danger_src AS '隱患來源', ledger.danger_type AS '隱患型別(專業)', ledger.danger_type1 AS '隱患型別(危害因素)', ledger.danger_type2 AS '隱患型別(體系)',
ledger.find_by_name AS '發現人', ledger.find_date AS '發現時間', ledger.registration_by_name AS '登記人', ledger.regist_time AS '登記時間', '自建系統' AS '系統型別', NOW() AS '匯出時間'
FROM dp_phd_ledger ledger
LEFT JOIN company_info info ON ledger.credit_code = info.credit_code AND info.deleted = 0 # 注意此處
WHERE ledger.deleted = 0 AND ledger.danger_type IS NOT NULL AND ledger.find_date >= '2023-05-01 00:00:00'
ORDER BY ledger.credit_code, DATE_FORMAT(ledger.find_date, '%Y-%m-%d')
錯誤寫法:
SELECT
ledger.credit_code AS '統一社會信用程式碼', info.company_name AS '企業名稱', ledger.hazard_ledger_id AS '主鍵',
(CASE ledger.danger_level WHEN 1 THEN '一級' WHEN 2 THEN '二級' WHEN 3 THEN '三級' WHEN 4 THEN '四級' ELSE ledger.danger_level END) AS '隱患等級',
(CASE ledger.manager_level WHEN 0 THEN '集團級' WHEN 1 THEN '公司級' WHEN 2 THEN '企業級' WHEN 3 THEN '車間級' WHEN 4 THEN '裝置級' ELSE ledger.manager_level END) AS '管理級別',
ledger.danger_type AS '隱患型別分類編碼',
(CASE
WHEN ledger.danger_type LIKE '1-%' THEN '健康'
WHEN ledger.danger_type LIKE '2-%' THEN '安全'
WHEN ledger.danger_type LIKE '3-%' THEN '環保'
WHEN ledger.danger_type LIKE '4-%' THEN '節能'
WHEN ledger.danger_type LIKE '5-%' THEN '低碳'
WHEN ledger.danger_type LIKE '6-%' THEN '安保'
ELSE ledger.danger_type
END) AS '隱患型別一級分類',
-- ledger.danger_name AS '隱患名稱', ledger.danger_description AS '隱患描述', ledger.danger_src AS '隱患來源', ledger.danger_type AS '隱患型別(專業)', ledger.danger_type1 AS '隱患型別(危害因素)', ledger.danger_type2 AS '隱患型別(體系)',
ledger.find_by_name AS '發現人', ledger.find_date AS '發現時間', ledger.registration_by_name AS '登記人', ledger.regist_time AS '登記時間', '自建系統' AS '系統型別', NOW() AS '匯出時間'
FROM dp_phd_ledger ledger
LEFT JOIN company_info info ON ledger.credit_code = info.credit_code
# 注意此處
WHERE info.deleted = 0 AND ledger.deleted = 0 AND ledger.danger_type IS NOT NULL AND ledger.find_date >= '2023-05-01 00:00:00'
ORDER BY ledger.credit_code, DATE_FORMAT(ledger.find_date, '%Y-%m-%d')
對比正確的連表查詢條件寫法,錯誤的寫法中,將右表的 info.deleted = 0 條件放到了 WHERE 中,正確的寫法則是放在了 ON 中,原因:dp_phd_ledger 為左表,company_info 表為右表,LEFT JOIN 時,company_info 表可能沒有資料,此時,company_info 表的 deleted 欄位為 NULL,不滿足 deleted = 0 條件,資料會丟失
。而將 info.deleted = 0 條件放到 ON 中,會先過濾 company_info 表的資料,此時不會丟失資料。
子查詢
子查詢指一個查詢語句巢狀在另一個查詢語句內部的查詢,這個特性從 MySQL 4.1 開始引入。
SQL 中子查詢的使用大大增強了 SELECT 查詢的能力,因為很多時候查詢需要從結果集中獲取資料,或者需要從同一個表中先計算得出一個資料結果,然後與這個資料結果(可能是某個標量,也可能是某個集合)進行比較。
需求分析與問題解決
實際問題
現有解決方式:
# 方式一
mysql> SELECT salary FROM employees WHERE last_name = 'Abel';
+----------+
| salary |
+----------+
| 11000.00 |
+----------+
1 row in set (0.00 sec)
mysql> SELECT last_name, salary FROM employees WHERE salary > 11000;
+-----------+----------+
| last_name | salary |
+-----------+----------+
| King | 24000.00 |
| Kochhar | 17000.00 |
| De Haan | 17000.00 |
| Greenberg | 12000.00 |
| Russell | 14000.00 |
| Partners | 13500.00 |
| Errazuriz | 12000.00 |
| Ozer | 11500.00 |
| Hartstein | 13000.00 |
| Higgins | 12000.00 |
+-----------+----------+
10 rows in set (0.00 sec)
# 方式二:自連線
mysql> SELECT e2.last_name, e2.salary FROM employees e1, employees e2 WHERE e1.last_name = 'Abel' AND e1.`salary` < e2.`salary`;
+-----------+----------+
| last_name | salary |
+-----------+----------+
| King | 24000.00 |
| Kochhar | 17000.00 |
| De Haan | 17000.00 |
| Greenberg | 12000.00 |
| Russell | 14000.00 |
| Partners | 13500.00 |
| Errazuriz | 12000.00 |
| Ozer | 11500.00 |
| Hartstein | 13000.00 |
| Higgins | 12000.00 |
+-----------+----------+
10 rows in set (0.01 sec)
# 方式三:子查詢
mysql> SELECT last_name, salary FROM employees WHERE salary > (SELECT salary FROM employees WHERE last_name = 'Abel');
+-----------+----------+
| last_name | salary |
+-----------+----------+
| King | 24000.00 |
| Kochhar | 17000.00 |
| De Haan | 17000.00 |
| Greenberg | 12000.00 |
| Russell | 14000.00 |
| Partners | 13500.00 |
| Errazuriz | 12000.00 |
| Ozer | 11500.00 |
| Hartstein | 13000.00 |
| Higgins | 12000.00 |
+-----------+----------+
10 rows in set (0.00 sec)
子查詢的基本使用
子查詢的基本語法結構:
- 子查詢(內查詢)在主查詢之前一次執行完成。
- 子查詢的結果被主查詢(外查詢)使用。
注意事項:
- 子查詢要包含在括號內。
- 將子查詢放在比較條件的右側。
- 單行運算子對應單行子查詢,多行運算子對應多行子查詢。
子查詢的分類
分類方式一:按內查詢的結果返回一條還是多條記錄,將子查詢分為單行子查詢
、多行子查詢
。
-
單行子查詢
-
多行子查詢
分類方式二:按內查詢是否被執行多次,將子查詢劃分為相關(或關聯)子查詢
和不相關(或非關聯)子查詢
。
- 子查詢從資料表中查詢了資料結果,如果這個資料結果只執行一次,然後這個資料結果作為主查詢的條件進行執行,那麼這樣的子查詢叫做不相關子查詢。
- 同樣,如果子查詢需要執行多次,即採用迴圈的方式,先從外部查詢開始,每次都傳入子查詢進行查詢,然後再將結果反饋給外部,這種巢狀的執行方式就稱為相關子查詢。
單行子查詢
單行比較運算子
運算子 | 含義 |
---|---|
= | equal to |
> | greater than |
>= | greater than or equal to |
< | less than |
<= | less than or equal to |
<> | not equal to |
程式碼示例
查詢工資大於 149 號員工工資的員工的資訊:
mysql> SELECT last_name, salary FROM employees WHERE salary > (SELECT salary FROM employees WHERE employee_id = 149);
+-----------+----------+
| last_name | salary |
+-----------+----------+
| King | 24000.00 |
| Kochhar | 17000.00 |
| De Haan | 17000.00 |
| Greenberg | 12000.00 |
| Raphaely | 11000.00 |
| Russell | 14000.00 |
| Partners | 13500.00 |
| Errazuriz | 12000.00 |
| Cambrault | 11000.00 |
| Ozer | 11500.00 |
| Abel | 11000.00 |
| Hartstein | 13000.00 |
| Higgins | 12000.00 |
+-----------+----------+
13 rows in set (0.00 sec)
返回 job_id 與 141 號員工相同,salary 比 143 號員工多的員工姓名,job_id 和工資:
mysql> SELECT last_name, job_id, salary FROM employees WHERE job_id = (SELECT job_id FROM employees WHERE employee_id = 141) AND salary > (SELECT salary FROM employees WHERE employee_id = 143);
+-------------+----------+---------+
| last_name | job_id | salary |
+-------------+----------+---------+
| Nayer | ST_CLERK | 3200.00 |
| Mikkilineni | ST_CLERK | 2700.00 |
| Bissot | ST_CLERK | 3300.00 |
| Atkinson | ST_CLERK | 2800.00 |
| Mallin | ST_CLERK | 3300.00 |
| Rogers | ST_CLERK | 2900.00 |
| Ladwig | ST_CLERK | 3600.00 |
| Stiles | ST_CLERK | 3200.00 |
| Seo | ST_CLERK | 2700.00 |
| Rajs | ST_CLERK | 3500.00 |
| Davies | ST_CLERK | 3100.00 |
+-------------+----------+---------+
11 rows in set (0.00 sec)
返回公司工資最少的員工的 last_name,job_id 和 salary:
mysql> SELECT last_name, job_id, salary FROM employees WHERE salary = (SELECT MIN(salary) FROM employees);
+-----------+----------+---------+
| last_name | job_id | salary |
+-----------+----------+---------+
| Olson | ST_CLERK | 2100.00 |
+-----------+----------+---------+
1 row in set (0.00 sec)
查詢與 141 號或 174 號員工的 manager_id 和 department_id 相同的其他員工的 employee_id,manager_id,department_id:
# 方式一
mysql> SELECT employee_id, manager_id, department_id FROM employees WHERE manager_id IN (SELECT manager_id FROM employees WHERE employee_id IN (174,141)) AND department_id IN (SELECT department_id FROM employees WHERE employee_id IN (174,141)) AND employee_id NOT IN (174,141);
+-------------+------------+---------------+
| employee_id | manager_id | department_id |
+-------------+------------+---------------+
| 142 | 124 | 50 |
| 143 | 124 | 50 |
| 144 | 124 | 50 |
| 196 | 124 | 50 |
| 197 | 124 | 50 |
| 198 | 124 | 50 |
| 199 | 124 | 50 |
| 175 | 149 | 80 |
| 176 | 149 | 80 |
| 177 | 149 | 80 |
| 179 | 149 | 80 |
+-------------+------------+---------------+
11 rows in set (0.00 sec)
# 方式二
mysql> SELECT employee_id, manager_id, department_id FROM employees WHERE (manager_id, department_id) IN (SELECT manager_id, department_id FROM employees WHERE employee_id IN (141,174)) AND employee_id NOT IN (141,174);
+-------------+------------+---------------+
| employee_id | manager_id | department_id |
+-------------+------------+---------------+
| 142 | 124 | 50 |
| 143 | 124 | 50 |
| 144 | 124 | 50 |
| 196 | 124 | 50 |
| 197 | 124 | 50 |
| 198 | 124 | 50 |
| 199 | 124 | 50 |
| 175 | 149 | 80 |
| 176 | 149 | 80 |
| 177 | 149 | 80 |
| 179 | 149 | 80 |
+-------------+------------+---------------+
11 rows in set (0.00 sec)
HAVING 中的子查詢
執行過程:
- 首先,執行子查詢;
- 然後,向主查詢中的 HAVING 子句返回結果。
查詢最低工資大於 50 號部門最低工資的部門 id 和其最低工資:
mysql> SELECT department_id, MIN(salary) FROM employees GROUP BY department_id HAVING MIN(salary) > (SELECT MIN(salary) FROM employees WHERE department_id = 50);
+---------------+-------------+
| department_id | MIN(salary) |
+---------------+-------------+
| NULL | 7000.00 |
| 10 | 4400.00 |
| 20 | 6000.00 |
| 30 | 2500.00 |
| 40 | 6500.00 |
| 60 | 4200.00 |
| 70 | 10000.00 |
| 80 | 6100.00 |
| 90 | 17000.00 |
| 100 | 6900.00 |
| 110 | 8300.00 |
+---------------+-------------+
11 rows in set (0.00 sec)
CASE 中的子查詢
查詢員工的 employee_id,last_name 和 location。其中,若員工 department_id 與 location_id 為1800 的員工的 department_id 相同,則 location 為 Canada,其餘則為 USA。
mysql> SELECT employee_id, last_name, (CASE department_id WHEN (SELECT department_id FROM departments WHERE location_id = 1800) THEN 'Canada' ELSE 'USA' END) location FROM employees;
+-------------+-------------+----------+
| employee_id | last_name | location |
+-------------+-------------+----------+
| 100 | King | USA |
| 101 | Kochhar | USA |
| 102 | De Haan | USA |
| 103 | Hunold | USA |
| 104 | Ernst | USA |
| 105 | Austin | USA |
| 106 | Pataballa | USA |
| 107 | Lorentz | USA |
| 108 | Greenberg | USA |
| 109 | Faviet | USA |
| 110 | Chen | USA |
| 111 | Sciarra | USA |
| 112 | Urman | USA |
| 113 | Popp | USA |
| 200 | Whalen | USA |
| 201 | Hartstein | Canada |
| 202 | Fay | Canada |
| 203 | Mavris | USA |
| 204 | Baer | USA |
| 205 | Higgins | USA |
| 206 | Gietz | USA |
+-------------+-------------+----------+
107 rows in set (0.00 sec)
空值問題
mysql> SELECT last_name, job_id FROM employees WHERE job_id = (SELECT job_id FROM employees WHERE last_name = 'Haas');
Empty set (0.00 sec)
mysql> SELECT job_id FROM employees WHERE last_name = 'Haas';
Empty set
employees 中沒有叫 Haas 的員工,子查詢不返回任何行,主查詢也為空值。
非法使用子查詢
mysql> SELECT employee_id, last_name FROM employees WHERE salary = (SELECT MIN(salary) FROM employees GROUP BY department_id);
ERROR 1242 (21000): Subquery returns more than 1 row
多行子查詢,但使用了單行比較符。
多行子查詢
多行子查詢,也稱為集合比較子查詢,其特點如下:
- 內查詢返回多行。
- 使用多行比較運算子。
多行比較運算子
運算子 | 含義 |
---|---|
IN | 等於列表中的任意一個 |
ANY | 需要和單行比較運算子一起使用,和子查詢返回的某一個 值比較 |
ALL | 需要和單行比較運算子一起使用,和子查詢返回的所有 值比較 |
SOME | 實際上是 ANY 的別名,作用相同,一般常使用 ANY |
程式碼示例
返回其它 job_id 中比 job_id 為 IT_PROG 部門任一工資低的員工的員工號、姓名、job_id 以及 salary:
mysql> SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary < ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG';
+-------------+-------------+------------+---------+
| employee_id | last_name | job_id | salary |
+-------------+-------------+------------+---------+
| 110 | Chen | FI_ACCOUNT | 8200.00 |
| 111 | Sciarra | FI_ACCOUNT | 7700.00 |
| 112 | Urman | FI_ACCOUNT | 7800.00 |
| 113 | Popp | FI_ACCOUNT | 6900.00 |
| 115 | Khoo | PU_CLERK | 3100.00 |
| 116 | Baida | PU_CLERK | 2900.00 |
| 117 | Tobias | PU_CLERK | 2800.00 |
| 118 | Himuro | PU_CLERK | 2600.00 |
| 119 | Colmenares | PU_CLERK | 2500.00 |
| 120 | Weiss | ST_MAN | 8000.00 |
| 121 | Fripp | ST_MAN | 8200.00 |
| 122 | Kaufling | ST_MAN | 7900.00 |
| 123 | Vollman | ST_MAN | 6500.00 |
| 124 | Mourgos | ST_MAN | 5800.00 |
| 125 | Nayer | ST_CLERK | 3200.00 |
| 203 | Mavris | HR_REP | 6500.00 |
| 206 | Gietz | AC_ACCOUNT | 8300.00 |
+-------------+-------------+------------+---------+
76 rows in set (0.00 sec)
mysql> SELECT salary FROM employees WHERE job_id = 'IT_PROG';
+---------+
| salary |
+---------+
| 9000.00 |
| 6000.00 |
| 4800.00 |
| 4800.00 |
| 4200.00 |
+---------+
5 rows in set (0.00 sec)
返回其它 job_id 中比 job_id 為 IT_PROG 部門所有工資都低的員工的員工號、姓名、job_id 以及 salary:
mysql> SELECT employee_id, last_name, job_id, salary FROM employees WHERE salary < ALL (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG';
+-------------+-------------+----------+---------+
| employee_id | last_name | job_id | salary |
+-------------+-------------+----------+---------+
| 115 | Khoo | PU_CLERK | 3100.00 |
| 116 | Baida | PU_CLERK | 2900.00 |
| 117 | Tobias | PU_CLERK | 2800.00 |
| 118 | Himuro | PU_CLERK | 2600.00 |
| 119 | Colmenares | PU_CLERK | 2500.00 |
| 125 | Nayer | ST_CLERK | 3200.00 |
| 126 | Mikkilineni | ST_CLERK | 2700.00 |
| 127 | Landry | ST_CLERK | 2400.00 |
| 128 | Markle | ST_CLERK | 2200.00 |
| 129 | Bissot | ST_CLERK | 3300.00 |
| 130 | Atkinson | ST_CLERK | 2800.00 |
| 131 | Marlow | ST_CLERK | 2500.00 |
| 132 | Olson | ST_CLERK | 2100.00 |
| 193 | Everett | SH_CLERK | 3900.00 |
| 194 | McCain | SH_CLERK | 3200.00 |
| 195 | Jones | SH_CLERK | 2800.00 |
| 196 | Walsh | SH_CLERK | 3100.00 |
| 197 | Feeney | SH_CLERK | 3000.00 |
| 198 | OConnell | SH_CLERK | 2600.00 |
| 199 | Grant | SH_CLERK | 2600.00 |
+-------------+-------------+----------+---------+
44 rows in set (0.00 sec)
查詢平均工資最低的部門 id:
# 方式一
mysql> SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = (SELECT MIN(avg_sal) FROM (SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id) t_dept_avg_sal);
+---------------+
| department_id |
+---------------+
| 50 |
+---------------+
1 row in set (0.00 sec)
# 方式二
mysql> SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = (SELECT MIN(avg_sal) FROM (SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id) t_dept_avg_sal);
+---------------+
| department_id |
+---------------+
| 50 |
+---------------+
1 row in set (0.00 sec)
MySQL 中聚合函式不能巢狀使用。
空值問題
mysql> SELECT last_name FROM employees WHERE employee_id NOT IN (SELECT manager_id FROM employees);
Empty set (0.00 sec)
mysql> SELECT manager_id FROM employees;
+------------+
| manager_id |
+------------+
| NULL |
| 100 |
| 100 |
| 100 |
| 149 |
| 149 |
| 201 |
| 205 |
+------------+
107 rows in set (0.00 sec)
mysql> SELECT last_name FROM employees WHERE employee_id NOT IN (SELECT manager_id FROM employees WHERE manager_id IS NOT NULL);
+-------------+
| last_name |
+-------------+
| Ernst |
| Austin |
| Pataballa |
| Lorentz |
| Faviet |
| Mavris |
| Baer |
| Gietz |
+-------------+
89 rows in set (0.00 sec)
因為子查詢中,返回了一個 NULL 值,導致主查詢失敗。新增條件後,主查詢正常。
相關子查詢
相關子查詢執行流程
如果子查詢的執行依賴於外部查詢,通常情況下都是因為子查詢中的表用到了外部的表,並進行了條件關聯,因此每執行一次外部查詢,子查詢都要重新計算一次,這樣的子查詢就稱之為關聯子查詢
。
相關子查詢按照一行接一行的順序執行,主查詢的每一行都執行一次子查詢。
程式碼示例
查詢員工中工資大於本部門平均工資的員工的 last_name,salary 和其 department_id:
# 相關子查詢
mysql> SELECT e1.last_name, e1.salary, e1.department_id FROM employees e1 WHERE salary > (SELECT AVG(salary) FROM employees e2 WHERE e2.department_id = e1.department_id);
+-----------+----------+---------------+
| last_name | salary | department_id |
+-----------+----------+---------------+
| King | 24000.00 | 90 |
| Hunold | 9000.00 | 60 |
| Ernst | 6000.00 | 60 |
| Greenberg | 12000.00 | 100 |
| Faviet | 9000.00 | 100 |
| Raphaely | 11000.00 | 30 |
| Weiss | 8000.00 | 50 |
| Fripp | 8200.00 | 50 |
| Kaufling | 7900.00 | 50 |
| Vollman | 6500.00 | 50 |
| Mourgos | 5800.00 | 50 |
| Ladwig | 3600.00 | 50 |
| Rajs | 3500.00 | 50 |
| Russell | 14000.00 | 80 |
| Partners | 13500.00 | 80 |
| Errazuriz | 12000.00 | 80 |
| Cambrault | 11000.00 | 80 |
| Zlotkey | 10500.00 | 80 |
| Tucker | 10000.00 | 80 |
| Bernstein | 9500.00 | 80 |
| Hall | 9000.00 | 80 |
| King | 10000.00 | 80 |
| Sully | 9500.00 | 80 |
| McEwen | 9000.00 | 80 |
| Vishney | 10500.00 | 80 |
| Greene | 9500.00 | 80 |
| Ozer | 11500.00 | 80 |
| Bloom | 10000.00 | 80 |
| Fox | 9600.00 | 80 |
| Abel | 11000.00 | 80 |
| Sarchand | 4200.00 | 50 |
| Bull | 4100.00 | 50 |
| Chung | 3800.00 | 50 |
| Dilly | 3600.00 | 50 |
| Bell | 4000.00 | 50 |
| Everett | 3900.00 | 50 |
| Hartstein | 13000.00 | 20 |
| Higgins | 12000.00 | 110 |
+-----------+----------+---------------+
38 rows in set (0.00 sec)
# 方式二:在 FROM 中使用子查詢
mysql> SELECT e1.last_name, e1.salary, e1.department_id FROM employees e1, (SELECT department_id, AVG(salary) dept_avg_sal FROM employees GROUP
BY department_id) e2 WHERE e1.department_id = e2.department_id AND e2.dept_avg_sal < e1.salary;
+-----------+----------+---------------+
| last_name | salary | department_id |
+-----------+----------+---------------+
| Hartstein | 13000.00 | 20 |
| Raphaely | 11000.00 | 30 |
| Weiss | 8000.00 | 50 |
| Fripp | 8200.00 | 50 |
| Kaufling | 7900.00 | 50 |
| Vollman | 6500.00 | 50 |
| Mourgos | 5800.00 | 50 |
| Ladwig | 3600.00 | 50 |
| Rajs | 3500.00 | 50 |
| Sarchand | 4200.00 | 50 |
| Bull | 4100.00 | 50 |
| Chung | 3800.00 | 50 |
| Dilly | 3600.00 | 50 |
| Bell | 4000.00 | 50 |
| Everett | 3900.00 | 50 |
| Hunold | 9000.00 | 60 |
| Ernst | 6000.00 | 60 |
| Russell | 14000.00 | 80 |
| Partners | 13500.00 | 80 |
| Errazuriz | 12000.00 | 80 |
| Cambrault | 11000.00 | 80 |
| Zlotkey | 10500.00 | 80 |
| Tucker | 10000.00 | 80 |
| Bernstein | 9500.00 | 80 |
| Hall | 9000.00 | 80 |
| King | 10000.00 | 80 |
| Sully | 9500.00 | 80 |
| McEwen | 9000.00 | 80 |
| Vishney | 10500.00 | 80 |
| Greene | 9500.00 | 80 |
| Ozer | 11500.00 | 80 |
| Bloom | 10000.00 | 80 |
| Fox | 9600.00 | 80 |
| Abel | 11000.00 | 80 |
| King | 24000.00 | 90 |
| Greenberg | 12000.00 | 100 |
| Faviet | 9000.00 | 100 |
| Higgins | 12000.00 | 110 |
+-----------+----------+---------------+
38 rows in set (0.00 sec)
FROM 型的子查詢:子查詢是作為 FROM 的一部分,子查詢要用 () 引起來,並且要給這個子查詢取別名, 把它當成一張 "臨時的虛擬的表" 來使用。(主表與虛擬表關聯查詢)
查詢員工的 id,salary,按照 department_name 排序:
# 在 ORDER BY 中使用子查詢
mysql> SELECT e.employee_id, e.salary FROM employees e ORDER BY (SELECT department_name FROM departments d WHERE e.department_id = d.department_id);
+-------------+----------+
| employee_id | salary |
+-------------+----------+
| 178 | 7000.00 |
| 205 | 12000.00 |
| 206 | 8300.00 |
| 200 | 4400.00 |
| 100 | 24000.00 |
| 101 | 17000.00 |
| 102 | 17000.00 |
| 108 | 12000.00 |
| 109 | 9000.00 |
| 110 | 8200.00 |
| 111 | 7700.00 |
| 196 | 3100.00 |
| 197 | 3000.00 |
| 198 | 2600.00 |
| 199 | 2600.00 |
+-------------+----------+
107 rows in set (0.00 sec)
在SELECT中,除了GROUP BY 和 LIMIT之外,其他位置都可以宣告子查詢!
若 employees 表中 employee_id 與 job_history 表中 employee_id 相同的數目不小於 2,輸出這些相同 id 的員工的 employee_id,last_name 和其 job_id:
mysql> SELECT e.employee_id, e.last_name, e.job_id FROM employees e WHERE 2 <= (SELECT COUNT(*) FROM job_history WHERE employee_id = e.employee_id);
+-------------+-----------+---------+
| employee_id | last_name | job_id |
+-------------+-----------+---------+
| 101 | Kochhar | AD_VP |
| 176 | Taylor | SA_REP |
| 200 | Whalen | AD_ASST |
+-------------+-----------+---------+
3 rows in set (0.00 sec)
EXISTS 與 NOT EXISTS 關鍵字
關聯子查詢通常也會和 EXISTS 運算子一起來使用,用來檢查在子查詢中是否存在滿足條件的行。
-
如果在子查詢中不存在滿足條件的行:
- 條件返回 FALSE;
- 繼續在子查詢中查詢。(不滿足繼續找)
-
如果在子查詢中存在滿足條件的行:
- 條件返回 TRUE;
- 不在子查詢中繼續查詢。(滿足即返回)
NOT EXISTS 關鍵字表示如果不存在某種條件,則返回 TRUE,否則返回 FALSE。
查詢公司管理者的 employee_id,last_name,job_id,department_id 資訊:
# 方式一:自連線
mysql> SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id FROM employees e1 JOIN employees e2 WHERE e1.employee_id
= e2.manager_id;
+-------------+-----------+---------+---------------+
| employee_id | last_name | job_id | department_id |
+-------------+-----------+---------+---------------+
| 100 | King | AD_PRES | 90 |
| 101 | Kochhar | AD_VP | 90 |
| 102 | De Haan | AD_VP | 90 |
| 103 | Hunold | IT_PROG | 60 |
| 108 | Greenberg | FI_MGR | 100 |
| 114 | Raphaely | PU_MAN | 30 |
| 120 | Weiss | ST_MAN | 50 |
| 121 | Fripp | ST_MAN | 50 |
| 122 | Kaufling | ST_MAN | 50 |
| 123 | Vollman | ST_MAN | 50 |
| 124 | Mourgos | ST_MAN | 50 |
| 145 | Russell | SA_MAN | 80 |
| 146 | Partners | SA_MAN | 80 |
| 147 | Errazuriz | SA_MAN | 80 |
| 148 | Cambrault | SA_MAN | 80 |
| 149 | Zlotkey | SA_MAN | 80 |
| 201 | Hartstein | MK_MAN | 20 |
| 205 | Higgins | AC_MGR | 110 |
+-------------+-----------+---------+---------------+
18 rows in set (0.00 sec)
# 方式二:存在一條記錄,employees 表中的 manager_id 等於 employee_id,即該 employee 為管理者
mysql> SELECT employee_id, last_name, job_id, department_id FROM employees e1 WHERE EXISTS (SELECT * FROM employees e2 WHERE e2.manager_id = e1.employee_id);
+-------------+-----------+---------+---------------+
| employee_id | last_name | job_id | department_id |
+-------------+-----------+---------+---------------+
| 100 | King | AD_PRES | 90 |
| 101 | Kochhar | AD_VP | 90 |
| 102 | De Haan | AD_VP | 90 |
| 103 | Hunold | IT_PROG | 60 |
| 108 | Greenberg | FI_MGR | 100 |
| 114 | Raphaely | PU_MAN | 30 |
| 120 | Weiss | ST_MAN | 50 |
| 121 | Fripp | ST_MAN | 50 |
| 122 | Kaufling | ST_MAN | 50 |
| 123 | Vollman | ST_MAN | 50 |
| 124 | Mourgos | ST_MAN | 50 |
| 145 | Russell | SA_MAN | 80 |
| 146 | Partners | SA_MAN | 80 |
| 147 | Errazuriz | SA_MAN | 80 |
| 148 | Cambrault | SA_MAN | 80 |
| 149 | Zlotkey | SA_MAN | 80 |
| 201 | Hartstein | MK_MAN | 20 |
| 205 | Higgins | AC_MGR | 110 |
+-------------+-----------+---------+---------------+
18 rows in set (0.00 sec)
# 方式三:相關子查詢
mysql> SELECT employee_id, last_name, job_id, department_id FROM employees WHERE employee_id IN (SELECT DISTINCT manager_id FROM employees);
+-------------+-----------+---------+---------------+
| employee_id | last_name | job_id | department_id |
+-------------+-----------+---------+---------------+
| 100 | King | AD_PRES | 90 |
| 101 | Kochhar | AD_VP | 90 |
| 102 | De Haan | AD_VP | 90 |
| 103 | Hunold | IT_PROG | 60 |
| 108 | Greenberg | FI_MGR | 100 |
| 114 | Raphaely | PU_MAN | 30 |
| 120 | Weiss | ST_MAN | 50 |
| 121 | Fripp | ST_MAN | 50 |
| 122 | Kaufling | ST_MAN | 50 |
| 123 | Vollman | ST_MAN | 50 |
| 124 | Mourgos | ST_MAN | 50 |
| 145 | Russell | SA_MAN | 80 |
| 146 | Partners | SA_MAN | 80 |
| 147 | Errazuriz | SA_MAN | 80 |
| 148 | Cambrault | SA_MAN | 80 |
| 149 | Zlotkey | SA_MAN | 80 |
| 201 | Hartstein | MK_MAN | 20 |
| 205 | Higgins | AC_MGR | 110 |
+-------------+-----------+---------+---------------+
18 rows in set (0.00 sec)
查詢 departments 表中,不存在於 employees 表中的部門的 department_id 和 department_name:
mysql> SELECT department_id, department_name FROM departments d WHERE NOT EXISTS (SELECT * FROM employees e WHERE e.department_id = d.department_id);
+---------------+----------------------+
| department_id | department_name |
+---------------+----------------------+
| 120 | Treasury |
| 130 | Corporate Tax |
| 140 | Control And Credit |
| 150 | Shareholder Services |
| 160 | Benefits |
| 170 | Manufacturing |
| 180 | Construction |
| 190 | Contracting |
| 200 | Operations |
| 210 | IT Support |
| 220 | NOC |
| 230 | IT Helpdesk |
| 240 | Government Sales |
| 250 | Retail Sales |
| 260 | Recruiting |
| 270 | Payroll |
+---------------+----------------------+
16 rows in set (0.00 sec)
相關更新
語法:
UPDATE table1 alias1
SET column = (SELECT expression FROM table2 alias2 WHERE alias1.column = alias2.column);
使用相關子查詢依據一個表中的資料更新另一個表的資料。
在 employees 中增加一個 department_name 欄位,資料為員工對應的部門名稱:
mysql> ALTER TABLE employees ADD(department_name VARCHAR(50));
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> UPDATE employees e SET department_name = (SELECT department_name FROM departments d WHERE e.department_id = d.department_id);
Query OK, 106 rows affected (0.01 sec)
相關刪除
語法:
DELETE FROM table1 alias1
WHERE column operator (SELECT expression FROM table2 alias2 WHERE alias1.column = alias2.column);
使用相關子查詢依據一個表中的資料刪除另一個表的資料。
刪除表 employees 中,與 emp_history 表皆有的資料:
mysql> DELETE FROM employees e WHERE e.employee_id IN (SELECT eh.employee_id FROM emp_history eh WHERE eh.employee_id = e.employee_id);
子查詢與自連線的對比
查詢工資比 Abel 高的員工資訊:
# 方式一:子查詢
mysql> SELECT last_name, salary FROM employees WHERE salary > (SELECT salary FROM employees WHERE last_name = 'Abel');
# 方式二:自連線
mysql> SELECT e2.last_name, e2.salary FROM employees e1, employees e2 WHERE e1.last_name = 'Abel' AND e1.salary < e2.salary;
此情況中,即可以使用子查詢,也可以使用自連線。一般情況建議使用自連線,因為在許多 DBMS 的處理過程中,對於自連線的處理速度要比子查詢快得多。
可以這樣理解:子查詢實際上是透過未知表進行查詢後的條件判斷,而自連線是透過已知的自身資料表進行條件判斷,因此在大部分 DBMS 中都對自連線處理進行了最佳化。
原文連結
https://github.com/ACatSmiling/zero-to-zero/blob/main/RelationalDatabase/mysql.md