Home
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

4

Local AI

--

Uedu Code

--

Taipei AQI 57 · Taichung AQI 74 · Tainan AQI 63 · Kaohsiung AQI 64
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 03

資料庫設計

實體、關聯、主鍵與外鍵,正規化到第三正規化
Interaction實作 25 minute(s)

Learning Objectives

  • 從需求找出實體、屬性與實體之間的關聯,分辨一對多與多對多
  • 說明主鍵、外鍵與實體、領域、參考三種完整性,並解讀外鍵擋下的錯誤(ERROR 1452、1451)
  • 用多對多的中介表格(assignment、employeeSkills)表示「員工替客戶工作」與「員工有多項技能」
  • 指出沒有正規化的表格會產生新增、更新、刪除異常
  • 依第一到第三正規化的定義,把一張大表格拆成 department、employee、employeeSkills

為什麼要先設計

前兩課看到的 employee 資料庫有五張表格:department、employee、employeeSkills、client、assignment。 為什麼不把所有資料放在一張大表格裡,一列記一位員工的全部資料就好?

一張大表格看起來簡單,但同一份資訊(例如「部門 128 叫 Research and Development」)會在很多列裡重複出現。 重複的資料不只浪費空間,更麻煩的是它們可能彼此矛盾:改了其中一列、漏了另一列,資料庫就不知道哪一個才對。

資料庫設計的目標是:每一項事實只存一次,表格之間用「鍵」連起來,需要時再用查詢(第 07 課的 JOIN)組合回來。 這一課先建立設計的概念,再用正規化的步驟把一張大表格拆開。

實體、屬性與關聯

設計資料庫時,先從需求裡找出三樣東西:

名詞 意思 在 employee 資料庫
實體(entity) 要記錄的「東西」,通常成為一張表格 部門、員工、客戶
屬性(attribute) 實體的性質,成為表格的欄位 員工的編號、姓名、職稱
關聯(relationship) 實體之間的關係 員工屬於部門;員工替客戶工作

關聯依照數量分成三種:

類型 例子 表格的做法
一對一(1:1) 一位員工對應一張員工證 合併成一張表格,或其中一張用另一張的主鍵當外鍵
一對多(1:N) 一個部門有很多員工,一位員工只屬於一個部門 在「多」的那一邊加外鍵:employee.departmentID
多對多(M:N) 一位員工可以替多個客戶工作,一個客戶也可以有多位員工 另外建一張中介表格,放兩邊的外鍵:assignment

employee 資料庫的關聯可以畫成:

department 1 ──── N employee 1 ──── N employeeSkills
                        │
                        N
                   assignment(clientID、employeeID、workdate、hours)
                        N
                        │
                     client 1

「一位員工有多項技能」也是一對多:技能不是單一個值,所以另外放在 employeeSkills,一項技能一列。

主鍵、外鍵與三種完整性

主鍵(primary key)是能唯一識別一列的欄位或欄位組合。MySQL 對主鍵有兩個要求:值不可重複、不可以是 NULL。 department 的主鍵是 departmentID;employeeSkills 的主鍵是 (employeeID, skill) 兩個欄位的組合,稱為複合主鍵。

外鍵(foreign key)是「指向另一張表格主鍵」的欄位。employee.departmentID 指向 department.departmentID: 每位員工的部門編號,必須是 department 表格裡真的存在的部門。

原課程把資料的正確性整理成三種完整性(integrity):

完整性 規則 MySQL 怎麼保證
實體完整性 每張表格有主鍵,主鍵唯一且不是 NULL PRIMARY KEY;重複時出現 ERROR 1062
領域完整性 每個欄位的值合乎型別、格式與範圍 資料型別、NOT NULL、CHECK(第 04 課)
參考完整性 外鍵指向的資料一定存在 FOREIGN KEY;指向不存在的列出現 ERROR 1452,刪除還被參考的列出現 ERROR 1451

外鍵只在 InnoDB 儲存引擎有作用(MySQL 8.0 的預設引擎)。MyISAM 表格會接受外鍵的寫法,但不會檢查(第 09 課)。

主鍵與外鍵擋下的錯誤

先看 department 與 employee 兩張表格,再試著加入一位部門編號 999 的員工、刪除還有員工的部門 42、加入一個編號重複的部門。三個錯誤分別是參考完整性與實體完整性在把關。

多對多:中介表格

「員工替客戶工作」是多對多。如果在 employee 加一個 clientID 欄位,一位員工就只能有一個客戶; 在 client 加 employeeID,一個客戶就只能有一位員工。兩邊都不對。

做法是另外建一張中介表格 assignment,一列代表「某位員工在某一天替某個客戶工作幾小時」:

欄位 角色
clientID 外鍵,指向 client
employeeID 外鍵,指向 employee
workdate 工作日期
hours 工作時數,屬於這一次派工的資料

主鍵是 (clientID, employeeID, workdate) 三個欄位的組合:同一位員工可以在同一天替不同客戶工作, 也可以在不同天替同一個客戶工作,但「同一位員工、同一天、同一個客戶」只能有一列。

employeeSkills(employeeID, skill) 的結構也一樣:主鍵是兩個欄位的組合,同一位員工可以有很多技能,但同一項技能不會記兩次。

assignment 的複合主鍵

先看 Nora(7513)的三項技能,以及 client 與 assignment 的內容。接著新增兩筆 1 月 21 日替 The Bank(客戶 2)的派工:Ben 和 Nora 各一筆,都成功;再替 Ben 新增同一天、同一個客戶的第二筆,就違反了複合主鍵。

想一想:鍵與關聯

1.

下面哪一個欄位不適合當作 employee 表格的主鍵?

2.

在 employee 資料庫執行下面的敘述,會得到什麼?(部門 999 不存在)

INSERT INTO employee VALUES (5001, 'Mia Chen', 'Analyst', 999);
3.

學生可以修很多門課,一門課也有很多學生,成績屬於「某位學生修某門課」。成績應該放在哪裡?

第 1 次作答

沒有正規化的表格:三種異常

假設把員工資料全部放進一張表格:

employee_flat(employeeID, name, job, departmentID, departmentName, skills)

departmentID 決定了 departmentName:128 一定是 Research and Development,42 一定是 Finance。 但部門名稱跟著每位員工重複存了好幾次,於是產生三種異常(anomaly):

異常 情況
新增異常 新增員工時要再打一次部門名稱,打錯(例如 42 打成 Research & Development)資料就矛盾;還沒有員工的新部門(129 Human Resources)根本記不進去,因為主鍵 employeeID 不能是 NULL
更新異常 部門 128 改名為 Emerging Technologies,只改了其中一列,同一個部門就有兩個名稱
刪除異常 刪除 Ben Smith(部門 42 唯一的員工),連「部門 42 叫 Finance」這項事實也一起消失

另外,skills 欄位把好幾項技能寫在同一格('C, Perl, Java'),想找「會 Java 的員工」只能用 LIKE '%Java%', 連 JavaScript 也會被找出來;想刪掉其中一項技能,也得改寫整個字串。

親眼看看三種異常

執行前已經在 design 資料庫建好 employee_flat,資料和 employee 資料庫相同,只是全部擠在一張表格裡。注意右邊表格的 departmentName 欄位:每執行一個敘述,部門名稱的一致性就被破壞一點。

正規化:第一到第三正規化

正規化(normalization)是一套逐步拆分表格的規則,每一步消除一種會造成異常的相依關係。 先說明功能相依:如果知道 A 的值就能確定 B 的值,稱為「B 相依於 A」,寫成 A → B。例如 departmentID → departmentName。

正規化 條件 做法
第一正規化(1NF) 每一格只放一個值(atomic),沒有重複的欄位群組 一格多個值的欄位,拆成多列
第二正規化(2NF) 符合 1NF,而且非鍵欄位完全相依於整個主鍵,不能只相依於複合主鍵的一部分 把部分相依的欄位移到新表格
第三正規化(3NF) 符合 2NF,而且非鍵欄位只相依於主鍵,不能透過另一個非鍵欄位間接相依(遞移相依) 把遞移相依的欄位移到新表格

以投影片的例子一步一步做:

原始表格:employee(employeeID, name, job, departmentID, departmentName, skill),skill 一格放好幾項技能。

1NF:skill 拆成一項一列,主鍵變成 (employeeID, skill)。Nora 有 3 項技能,就變成 3 列,姓名、部門跟著重複 3 次。

2NF:主鍵是 (employeeID, skill),但 name、job、departmentID、departmentName 只要知道 employeeID 就確定了, 和 skill 無關,這是部分相依。把它們移出去:

  • employeeSkills(employeeID, skill)
  • employee(employeeID, name, job, departmentID, departmentName)

3NF:在新的 employee 表格裡,employeeID → departmentID,而 departmentID → departmentName, 所以 departmentName 是經由 departmentID 間接相依於主鍵,這是遞移相依。把部門名稱移出去:

  • employee(employeeID, name, job, departmentID)
  • department(departmentID, departmentName)

結果就是 employee 資料庫的三張表格。原本的投影片把「拆出 department」放在第二正規化; 嚴格依照定義,它是遞移相依,屬於第三正規化,最後拆出來的表格相同。

正規化到 3NF 之後,每一項事實只存在一個地方:部門名稱只在 department 存一次,改名只要改一列,沒有員工的部門也記得進去。

把 1NF 的表格拆成三張表格

執行前已經建好符合 1NF 的 employee_1nf(一項技能一列,共 8 列)。依序建立 department、employee、employeeSkills,用 INSERT … SELECT 從原表格複製資料:DISTINCT 讓重複的部門、重複的員工只留一列。因為外鍵要指向已經存在的表格,建立的順序是 department → employee → employeeSkills。最後一行驗證拆開之後的外鍵確實在把關。

想一想:異常與正規化

1.

employee_flat 原本有 2 種部門編號與名稱的組合。執行下面的 UPDATE 之後,SELECT DISTINCT departmentID, departmentName 會得到幾列?

UPDATE employee_flat SET departmentName = 'Emerging Technologies'
WHERE employeeID = 7513;

(部門 128 有 3 位員工,7513 是其中一位)

2.

student(studentID, name, phones) 的 phones 欄位存的是 '0912-345678, 03-4227151'。這張表格違反哪一個正規化?

3.

在 employee(employeeID, name, job, departmentID, departmentName) 中,departmentName 和主鍵 employeeID 是什麼關係?

第 1 次作答

練習:訂單表格

投影片的練習題是一張訂單表格:

order(date, customerID, customerName, productID, productName, price, quantity)

找出功能相依:

  • customerID → customerName:知道顧客編號就知道顧客姓名
  • productID → productName, price:知道商品編號就知道品名與單價
  • quantity 要知道「哪一筆訂單的哪一項商品」才能確定

拆開之後:

  • customer(customerID, customerName)
  • product(productID, productName, price)
  • orders(orderID, orderDate, customerID, productID, quantity):新增流水號 orderID 當主鍵

這是投影片的答案,它假設一張訂單只買一種商品。如果一張訂單可以買好幾種商品, orderDate 與 customerID 只相依於 orderID,quantity 卻相依於 (orderID, productID),還要再拆成:

  • orders(orderID, orderDate, customerID)
  • orderItems(orderID, productID, quantity),主鍵是 (orderID, productID)

另外,order 是 SQL 的保留字(ORDER BY),拿來當表格名稱每次都得加反引號,所以改叫 orders。

動手寫:拆出 customer 表格

已經建好 shop 資料庫的 order_flat 表格(5 列訂單明細,同一位顧客的姓名重複出現)。請建立 customer 表格(customerID CHAR(3) 主鍵、customerName VARCHAR(20) NOT NULL),用 INSERT … SELECT DISTINCT 從 order_flat 複製顧客資料,最後依 customerID 排序列出 customer。

你的輸出
預期輸出

重點整理

概念 重點
實體、屬性、關聯 實體成為表格,屬性成為欄位;一對多在「多」的一邊放外鍵,多對多用中介表格
主鍵 唯一、不是 NULL;可以是多個欄位的組合(employeeSkills、assignment)
外鍵 指向另一張表格的主鍵;新增不存在的值 ERROR 1452,刪除還被參考的列 ERROR 1451
異常 資料重複存放造成新增、更新、刪除異常
1NF 一格一個值
2NF 沒有部分相依(非鍵欄位相依於整個主鍵)
3NF 沒有遞移相依(非鍵欄位只相依於主鍵)

正規化不是越拆越好:拆開之後查詢要用 JOIN 組回來。實務上通常做到 3NF, 少數為了查詢效能刻意保留重複資料的做法稱為反正規化(denormalization),要清楚知道代價才使用。

下一課用 CREATE DATABASE、CREATE TABLE 把這個設計實際建立起來。

這一課完成了!
Next lesson:建立資料庫、表格與索引
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。