資料庫經過正規化後,資料表之間便會產生關聯。本文將介紹關聯查詢,以一張表為基準,去關聯另一張表,藉此將內容組合在一起。接著介紹如何約束好兩張表之間的關聯,確保資料的一致與完整。
本文範例參考影片。
此篇亦轉載到個人部落格。
首先認識一下員工資料表的設計。
CREATE TABLE `employee` (
`emp_id` INT,
`name` VARCHAR(50),
`dept_id` INT,
`sup_id` INT,
PRIMARY KEY (`emp_id`)
);
INSERT INTO `employee` (`emp_id`, `name`, `dept_id`, `sup_id`)
VALUES
(206, "小黃", 1, null),
(207, "小綠", 2, 206),
(208, "小黑", 3, 206),
(209, "小白", 3, 207),
(210, "小蘭", 1, 207);
另外還有一個部門資料表,欄位包含編號、名稱與經理的員工編號。
CREATE TABLE `dept` (
`dept_id` INT,
`name` VARCHAR(50),
`manager_id` INT,
PRIMARY KEY (`dept_id`)
);
INSERT INTO `dept` (`dept_id`, `name`, `manager_id`)
VALUES
(1, "研發", 206),
(2, "行政", 207),
(3, "資訊", 208);
其中員工表的「dept_id」欄位代表部門編號,會關聯到部門表的「dept_id」欄位。而員工表的「sup_id」欄位代表主管編號,會關聯到另一筆員工資料。
由於 dept_id 與 sup_id 欄位會關聯到另一筆資料的主鍵(primary key),因此這兩個欄位被稱為「外鍵」(foreign key)。
以下指令是查詢每位員工及其所屬部門的資料。
SELECT *
FROM `employee`
JOIN `dept` ON `employee`.`dept_id` = `dept`.`dept_id`;
使用 JOIN ... ON ...
語法,將會以 FROM
指定的表為基準,去關聯 JOIN
指定的表。而關聯的條件,是員工表的外鍵等於部門表的主鍵。
以下指令是查詢每位員工的主管。
SELECT `emp`.`emp_id`, `emp`.`name`, `emp`.`sup_id`, `sup`.`name` AS `sup_name`
FROM `employee` AS `emp`
JOIN `employee` AS `sup` ON `emp`.`sup_id` = `sup`.`emp_id`;
這個例子比較特別,是資料表關聯自己。在撰寫指令時,要當成兩張表,並替它們取「別名」(alias)作為區隔。在此將員工表與主管表分別取為「emp」與「sup」。
另外在指定欄位時,也可使用別名,在 SELECT
與 ON
語法後方都可看見。
在前面查詢員工主管的例子中,會發現查不到「小黃」這個人。理由是 JOIN
取的是兩表的「交集」。小黃的 sup_id 外鍵欄位值是 null,對應不到任何資料,因此不會出現在結果中。
若我們希望關聯不到資料的小黃能出現在結果中,那麼可以使用 LEFT JOIN
語法。
SELECT `emp`.`emp_id`, `emp`.`name`, `emp`.`sup_id`, `sup`.`name` AS `sup_name`
FROM `employee` AS `emp`
LEFT JOIN `employee` AS `sup` ON `emp`.`sup_id` = `sup`.`emp_id`;
LEFT JOIN
的特色,是讓 FROM
指定的表的資料,都能出現在查詢結果中。如果外鍵關聯不到資料,則 JOIN 的表的相關欄位值便留空,也就是 null。
其語法為 RIGHT JOIN
,用途與 LEFT JOIN
相反。JOIN 指定的表的資料,都會出現在結果中。
這兩者是可以互相改寫的。
SELECT `emp`.`emp_id`, `emp`.`name`, `emp`.`sup_id`, `sup`.`name` AS `sup_name`
FROM `employee` AS `sup`
RIGHT JOIN `employee` AS `emp` ON `sup`.`emp_id` = `emp`.`sup_id`;
筆者認為 LEFT JOIN
更加直覺。
為了在兩張有關聯的表之間,維護資料的一致性與完整性,我們可以在外鍵與主鍵欄位添加「約束」(constraint)。
假設 A 表有個外鍵欄位關聯到 B 表主鍵。藉由添加約束,可以確保在 A 表插入或修改資料時,外鍵欄位值均能對應到 B 表的任一資料,達到了一致性。
以下是在建立員工表時添加約束,將部門編號欄位,關聯到部門表的編號欄位。
CREATE TABLE `employee` (
`emp_id` INT,
-- 略
`dept_id` INT,
FOREIGN KEY (`dept_id`) REFERENCES `dept`(`dept_id`)
);
執行上述指令的前提是部門表「dept」已經存在。然而部門表也有一個經理編號欄位(manager_id),要關聯到員工表的編號欄位(emp_id)。
當兩表互相約束,需透過事後修改資料表的方式來定義。
ALTER TABLE `employee`
ADD FOREIGN KEY (`dept_id`) REFERENCES `dept`(`dept_id`);
ALTER TABLE `dept`
ADD FOREIGN KEY (`manager_id`) REFERENCES `employee`(`emp_id`);
至於員工表的主管欄位,則屬於自己關聯自己,也可添加約束。
ALTER TABLE `employee`
ADD FOREIGN KEY (`sup_id`) REFERENCES `employee`(`emp_id`);
在刪除部門資料表時,由於它被具有外鍵的員工表參考,因此會無法刪除。
此時請先確認員工表的創建指令,找出這個約束的名稱。
SHOW CREATE TABLE `employee`;
在查詢結果中的 CONSTRAINT
語法後方,可得知約束的名稱,如「employee_ibfk_1」。
接著再執行如下的語法,即可移除約束。
ALTER TABLE `employee` DROP FOREIGN KEY `employee_ibfk_1`;
範例中的員工表對部門表進行了外鍵約束。假設直接刪除部門資料,或者更改編號,那就會造成員工資料不完整,畢竟對應不到部門。所以 MySQL 預設會禁止刪除部門資料,或更新其主鍵值。
然而我們可以做進一步的設定,去決定被參考的資料遇到上述的異動時,外部的資料要如何因應。
以下的指令,是讓部門表的編號欄位值更新時,能連動更新該部門的員工資料。而當部門資料被刪除,則將員工資料的部門欄位值設為 null。
ALTER TABLE `employee`
ADD FOREIGN KEY (`dept_id`) REFERENCES `dept`(`dept_id`)
ON UPDATE CASCADE
ON DELETE SET NULL;
此處使用 CASCADE
與 SET NULL
語法來定義處理方式。
同理,以下是針對員工的主管欄位做設定。
ALTER TABLE `employee`
ADD FOREIGN KEY (`sup_id`) REFERENCES `employee`(`emp_id`)
ON UPDATE CASCADE
ON DELETE SET NULL;
以第一節的測試資料為例,讓我們重新進行插入資料的步驟。
當資料表有設定外鍵約束,在插入時要格外留意,才不會操作失敗。可掌握以下兩個原則:
首先插入部門資料,但經理的員工編號(manager_id 欄位)暫時留空。
INSERT INTO `dept` (`dept_id`, `name`, `manager_id`)
VALUES
(1, "研發", null),
(2, "行政", null),
(3, "資訊", null);
接著輪到員工資料。此處先插入沒有主管的「小黃」,再將主管已在資料表中的其他員工陸續插入。
INSERT INTO `employee` (`emp_id`, `name`, `dept_id`, `sup_id`)
VALUES
(206, "小黃", 1, null),
(207, "小綠", 2, 206),
(208, "小黑", 3, 206),
(209, "小白", 3, 207),
(210, "小蘭", 1, 207);
當然也能暫時將主管編號(sup_id 欄位)設為 null,之後再用 UPDATE
指令更新。但這會額外進行寫入操作,因此建議能在插入時完成,就不需留到事後再更新。
最後回到部門表,補上經理的員工編號。
UPDATE `dept`
SET `manager_id` = 206
WHERE `dept_id` = 1;
UPDATE `dept`
SET `manager_id` = 207
WHERE `dept_id` = 2;
UPDATE `dept`
SET `manager_id` = 208
WHERE `dept_id` = 3;
這樣就完成在兩張表、三個約束的情況下,完成插入資料的流程。
今日文章到此結束!
最後宣傳一下自己的部落格,我是「新手工程師的程式教室」的作者,主要發表後端相關文章,請多指教