1. 程式人生 > 其它 >第06章_多表查詢

第06章_多表查詢

第06章_多表查詢

講師:尚矽谷-宋紅康(江湖人稱:康師傅)

官網:http://www.atguigu.com


多表查詢,也稱為關聯查詢,指兩個或更多個表一起完成查詢操作。

前提條件:這些一起查詢的表之間是有關係的(一對一、一對多),它們之間一定是有關聯欄位,這個關聯欄位可能建立了外來鍵,也可能沒有建立外來鍵。比如:員工表和部門表,這兩個表依靠“部門編號”進行關聯。

1. 一個案例引發的多表連線

1.1 案例說明

從多個表中獲取資料:

#案例:查詢員工的姓名及其部門名稱
SELECT last_name, department_name
FROM employees, departments;

查詢結果:

+-----------+----------------------+
| last_name | department_name      |
+-----------+----------------------+
| King      | Administration       |
| King      | Marketing            |
| King      | Purchasing           |
| King      | Human Resources      |
| King      | Shipping             |
| King      | IT                   |
| King      | Public Relations     |
| King      | Sales                |
| King      | Executive            |
| King      | Finance              |
| King      | Accounting           |
| King      | Treasury             |
...
| Gietz     | IT Support           |
| Gietz     | NOC                  |
| Gietz     | IT Helpdesk          |
| Gietz     | Government Sales     |
| Gietz     | Retail Sales         |
| Gietz     | Recruiting           |
| Gietz     | Payroll              |
+-----------+----------------------+
2889 rows in set (0.01 sec)

分析錯誤情況:

SELECT COUNT(employee_id) FROM employees;
#輸出107行

SELECT COUNT(department_id)FROM departments;
#輸出27行

SELECT 107*27 FROM dual;

我們把上述多表查詢中出現的問題稱為:笛卡爾積的錯誤。

1.2 笛卡爾積(或交叉連線)的理解

笛卡爾乘積是一個數學運算。假設我有兩個集合 X 和 Y,那麼 X 和 Y 的笛卡爾積就是 X 和 Y 的所有可能組合,也就是第一個物件來自於 X,第二個物件來自於 Y 的所有可能。組合的個數即為兩個集合中元素個數的乘積數。

SQL92中,笛卡爾積也稱為交叉連線

,英文是 CROSS JOIN。在 SQL99 中也是使用 CROSS JOIN表示交叉連線。它的作用就是可以把任意表進行連線,即使這兩張表不相關。在MySQL中如下情況會出現笛卡爾積:

#查詢員工姓名和所在部門名稱
SELECT last_name,department_name FROM employees,departments;
SELECT last_name,department_name FROM employees CROSS JOIN departments;
SELECT last_name,department_name FROM employees INNER JOIN departments;
SELECT last_name,department_name FROM employees JOIN departments;

1.3 案例分析與問題解決

  • 笛卡爾積的錯誤會在下面條件下產生

    • 省略多個表的連線條件(或關聯條件)
    • 連線條件(或關聯條件)無效
    • 所有表中的所有行互相連線
  • 為了避免笛卡爾積, 可以在 WHERE 加入有效的連線條件。

  • 加入連線條件後,查詢語法:

    SELECT	table1.column, table2.column
    FROM	table1, table2
    WHERE	table1.column1 = table2.column2;  #連線條件
    
    • 在 WHERE子句中寫入連線條件。
  • 正確寫法:

    #案例:查詢員工的姓名及其部門名稱
    SELECT last_name, department_name
    FROM employees, departments
    WHERE employees.department_id = departments.department_id;
    
  • 在表中有相同列時,在列名之前加上表名字首

2. 多表查詢分類講解

分類1:等值連線 vs 非等值連線

等值連線

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;

拓展1:多個連線條件與 AND 操作符

拓展2:區分重複的列名

  • 多個表中有相同列時,必須在列名之前加上表名字首。
  • 在不同表中具有相同列名的列可以用表名加以區分。
SELECT employees.last_name, departments.department_name,employees.department_id
FROM employees, departments
WHERE employees.department_id = departments.department_id;

拓展3:表的別名

  • 使用別名可以簡化查詢。

  • 列名前使用表名字首可以提高查詢效率。

SELECT e.employee_id, e.last_name, e.department_id,
       d.department_id, d.location_id
FROM   employees e , departments d
WHERE  e.department_id = d.department_id;

需要注意的是,如果我們使用了表的別名,在查詢欄位中、過濾條件中就只能使用別名進行代替,不能使用原有的表名,否則就會報錯。

阿里開發規範

強制】對於資料庫中表記錄的查詢和變更,只要涉及多個表,都需要在列名前加表的別名(或 表名)進行限定。

說明:對多表進行查詢記錄、更新記錄、刪除記錄時,如果對操作列沒有限定表的別名(或表名),並且操作列在多個表中存在時,就會拋異常。

正例: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。

拓展4:連線多個表

總結:連線 n個表,至少需要n-1個連線條件比如,連線三個表,至少需要兩個連線條件。

練習:查詢出公司員工的 last_name,department_name, city

非等值連線

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;

分類2:自連線 vs 非自連線

  • 當table1和table2本質上是同一張表,只是用取別名的方式虛擬成兩張表以代表不同的意義。然後兩個表再進行內連線,外連線等查詢。

題目:查詢employees表,返回“Xxx works for Xxx”

SELECT CONCAT(worker.last_name ,' works for ' 
       , manager.last_name)
FROM   employees worker, employees manager
WHERE  worker.manager_id = manager.employee_id ;

練習:查詢出last_name為 ‘Chen’ 的員工的 manager 的資訊。

分類3:內連線 vs 外連線

除了查詢滿足條件的記錄以外,外連線還可以查詢某一方不滿足條件的記錄。

  • 內連線: 合併具有同一列的兩個以上的表的行, 結果集中不包含一個表與另一個表不匹配的行

  • 外連線: 兩個表在連線過程中除了返回滿足連線條件的行以外還返回左(或右)表中不滿足條件的行 ,這種連線稱為左(或右) 外連線。沒有匹配的行時, 結果表中相應的列為空(NULL)。

  • 如果是左外連線,則連線條件中左邊的表也稱為主表,右邊的表稱為從表

    如果是右外連線,則連線條件中右邊的表也稱為主表,左邊的表稱為從表

SQL92:使用(+)建立連線

  • 在 SQL92 中採用(+)代表從表所在的位置。即左或右外連線中,(+) 表示哪個是從表。<兩條腿,不一樣長,短的墊一點+>

  • Oracle 對 SQL92 支援較好,而 MySQL 則不支援 SQL92 的外連線。

    #左外連線
    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;
    
  • 而且在 SQL92 中,只有左外連線和右外連線,沒有滿(或全)外連線。

3. SQL99語法實現多表查詢

3.1 基本語法

  • 使用JOIN...ON子句建立連線的語法結構:

    SELECT table1.column, table2.column,table3.column
    FROM table1
        JOIN table2 ON table1 和 table2 的連線條件
            JOIN table3 ON table2 和 table3 的連線條件
    

    它的巢狀邏輯類似我們使用的 FOR 迴圈:

    for t1 in table1:
        for t2 in table2:
           if condition1:
               for t3 in table3:
                  if condition2:
                      output t1 + t2 + t3
    

    SQL99 採用的這種巢狀結構非常清爽、層次性更強、可讀性更強,即使再多的表進行連線也都清晰可見。如果你採用 SQL92,可讀性就會大打折扣。

  • 語法說明:

    • 可以使用 ON 子句指定額外的連線條件
    • 這個連線條件是與其它條件分開的。
    • ON 子句使語句具有更高的易讀性
    • 關鍵字 JOIN、INNER JOIN、CROSS JOIN 的含義是一樣的,都表示內連線

3.2 內連線(INNER JOIN)的實現

  • 語法:
SELECT 欄位列表
FROM A表 INNER JOIN B表
ON 關聯條件
WHERE 等其他子句;

題目1:

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);

題目2:

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;

3.3 外連線(OUTER JOIN)的實現

3.3.1 左外連線(LEFT OUTER JOIN)

  • 語法:
#實現查詢結果是A
SELECT 欄位列表
FROM A表 LEFT JOIN B表
ON 關聯條件
WHERE 等其他子句;
  • 舉例:
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) ;

3.3.2 右外連線(RIGHT OUTER JOIN)

  • 語法:
#實現查詢結果是B
SELECT 欄位列表
FROM A表 RIGHT JOIN B表
ON 關聯條件
WHERE 等其他子句;
  • 舉例:
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) ;

需要注意的是,LEFT JOIN 和 RIGHT JOIN 只存在於 SQL99 及以後的標準中,在 SQL92 中不存在,只能用 (+) 表示。

3.3.3 滿外連線(FULL OUTER JOIN)

  • 滿外連線的結果 = 左右表匹配的資料 + 左表沒有匹配到的資料 + 右表沒有匹配到的資料。
  • SQL99是支援滿外連線的。使用FULL JOIN 或 FULL OUTER JOIN來實現。
  • 需要注意的是,MySQL不支援FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT join代替。

4. UNION的使用

合併查詢結果
利用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語句,以提高資料查詢的效率。

舉例:查詢部門編號>90或郵箱包含a的員工資訊

#方式1
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
#方式2
SELECT * FROM employees  WHERE email LIKE '%a%'
UNION
SELECT * FROM employees  WHERE department_id>90;

舉例:查詢中國使用者中男性的資訊以及美國使用者中年男性的使用者資訊

SELECT id,cname FROM t_chinamale WHERE csex='男'
UNION ALL
SELECT id,tname FROM t_usmale WHERE tGender='male';

5. 7種SQL JOINS的實現

5.7.1 程式碼實現

#中圖:內連線 A∩B
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`;
#左上圖:左外連線
SELECT employee_id,last_name,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`;
#右上圖:右外連線
SELECT employee_id,last_name,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`;
#左中圖:A - A∩B
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
#右中圖:B-A∩B
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
#左下圖:滿外連線
# 左中圖 + 右上圖  A∪B
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`;
#右下圖
#左中圖 + 右中圖  A ∪B- A∩B 或者 (A -  A∩B) ∪ (B - A∩B)
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

5.7.2 語法格式小結

  • 左中圖
#實現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 等其他子句;
  • 右下圖
#實現A∪B -  A∩B  或   (A -  A∩B) ∪ (B - A∩B)
#使用左外的 (A -  A∩B)  union 右外的(B - A∩B)
select 欄位列表
from A表 left join B表
on 關聯條件
where 從表關聯欄位 is null and 等其他子句

union

select 欄位列表
from A表 right join B表
on 關聯條件
where 從表關聯欄位 is null and 等其他子句

6. SQL99語法新特性

6.1 自然連線

SQL99 在 SQL92 的基礎上提供了一些特殊語法,比如 NATURAL JOIN 用來表示自然連線。我們可以把自然連線理解為 SQL92 中的等值連線。它會幫你自動查詢兩張連線表中所有相同的欄位,然後進行等值連線

在SQL92標準中:

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`;

在 SQL99 中你可以寫成:

SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;

6.2 USING連線

當我們進行連線的時候,SQL99還支援使用 USING 指定資料表裡的同名欄位進行等值連線。但是隻能配合JOIN一起使用。比如:

SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);

你能看出與自然連線 NATURAL JOIN 不同的是,USING 指定了具體的相同的欄位名稱,你需要在 USING 的括號 () 中填入要指定的同名欄位。同時使用 JOIN...USING 可以簡化 JOIN ON 的等值連線。它與下面的 SQL 查詢結果是相同的:

SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id;

7. 章節小結

表連線的約束條件可以有三種方式:WHERE, ON, USING

  • WHERE:適用於所有關聯查詢

  • ON:只能和JOIN一起使用,只能寫關聯條件。雖然關聯條件可以併到WHERE中和其他條件一起寫,但分開寫可讀性更好。

  • USING:只能和JOIN一起使用,而且要求兩個關聯欄位在關聯表中名稱一致,而且只能表示關聯欄位值相等

#關聯條件
#把關聯條件寫在where後面
SELECT last_name,department_name 
FROM employees,departments 
WHERE employees.department_id = departments.department_id;

#把關聯條件寫在on後面,只能和JOIN一起使用
SELECT last_name,department_name 
FROM employees INNER JOIN departments 
ON employees.department_id = departments.department_id;

SELECT last_name,department_name 
FROM employees CROSS JOIN departments 
ON employees.department_id = departments.department_id;

SELECT last_name,department_name  
FROM employees JOIN departments 
ON employees.department_id = departments.department_id;

#把關聯欄位寫在using()中,只能和JOIN一起使用
#而且兩個表中的關聯欄位必須名稱相同,而且只能表示=
#查詢員工姓名與基本工資
SELECT last_name,job_title
FROM employees INNER JOIN jobs USING(job_id);

#n張表關聯,需要n-1個關聯條件
#查詢員工姓名,基本工資,部門名稱
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;

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 中,也都會有最大連線表的限制。

【強制】超過三個表禁止 join。需要 join 的欄位,資料型別保持絕對一致;多表關聯查詢時, 保證被關聯的欄位需要有索引。

說明:即使雙表 join 也要注意表索引、SQL 效能。

來源:阿里巴巴《Java開發手冊》

附錄:常用的 SQL 標準有哪些

在正式開始講連線表的種類時,我們首先需要知道 SQL 存在不同版本的標準規範,因為不同規範下的表連線操作是有區別的。

SQL 有兩個主要的標準,分別是 SQL92SQL99。92 和 99 代表了標準提出的時間,SQL92 就是 92 年提出的標準規範。當然除了 SQL92 和 SQL99 以外,還存在 SQL-86、SQL-89、SQL:2003、SQL:2008、SQL:2011 和 SQL:2016 等其他的標準。

這麼多標準,到底該學習哪個呢?實際上最重要的 SQL 標準就是 SQL92 和 SQL99。一般來說 SQL92 的形式更簡單,但是寫的 SQL 語句會比較長,可讀性較差。而 SQL99 相比於 SQL92 來說,語法更加複雜,但可讀性更強。我們從這兩個標準釋出的頁數也能看出,SQL92 的標準有 500 頁,而 SQL99 標準超過了 1000 頁。實際上從 SQL99 之後,很少有人能掌握所有內容,因為確實太多了。就好比我們使用 Windows、Linux 和 Office 的時候,很少有人能掌握全部內容一樣。我們只需要掌握一些核心的功能,滿足日常工作的需求即可。

SQL92 和 SQL99 是經典的 SQL 標準,也分別叫做 SQL-2 和 SQL-3 標準。也正是在這兩個標準釋出之後,SQL 影響力越來越大,甚至超越了資料庫領域。現如今 SQL 已經不僅僅是資料庫領域的主流語言,還是資訊領域中資訊處理的主流語言。在圖形檢索、影象檢索以及語音檢索中都能看到 SQL 語言的使用。