學完正規化和限制之後,我們來做一個實戰練習。
這次的任務是:幫一間補習班設計班務系統的資料庫。
這個系統要記錄什麼?
- 有哪些課程(科目 + 年級)
- 有哪些老師
- 有哪些學生
- 老師在什麼時間、什麼教室上什麼課
- 學生選了哪些課
聽起來不難,但實際設計的時候,你會發現有很多細節要考慮。
讓我們一步步來。
找出有哪些個體
首先,我們要從需求中找出「有哪些東西」。
根據上面的描述,可以找出這些個體:
- 老師:要記錄有哪些老師
- 學生:要記錄有哪些學生
- 課程:由科目和年級組成(例如:三年級數學)
- 教室:老師在哪個教室上課
那「時間」呢?
時間比較特別。補習班的上課時間通常就是一個時間點,例如:「星期一 18:00」、「星期三 19:30」。
這些時間本身沒有什麼額外的性質需要記錄(不像教室有「容量」、老師有「姓名」),所以不一定需要另外切一張「時間表」,直接當作一個欄位就好。
那「科目」和「年級」呢?這要看情況,我們等一下再討論。
找出個體之間的關係
接下來,我們要想:這些個體之間有什麼關係?
什麼是講授
老師在特定的時間、特定的教室,教某一個課程。
這四個東西組合在一起,我們稱為「講授」。
例如:「David 老師在星期一 18:00,於 A101 教室,教三年級數學」就是一筆講授資料。
這是一個複雜的多對多關係:
- 一個老師可以教很多課程
- 一個課程可以被很多老師教(題目有特別強調這點)
- 一個課程可以在很多教室上
- 一個教室可以上很多課程(看時間安排)
課程、科目、年級的關係
課程是由科目和年級組成的。
例如:「三年級數學」這個課程 = 三年級 + 數學。
科目和年級也是多對多的關係:
- 一個科目有很多年級(數學有一年級數學、二年級數學、三年級數學…)
- 一個年級有很多科目(三年級有數學、英文、自然…)
學生選的是講授,不是課程
學生選課,選的是什麼?
直覺上你可能會覺得:學生選的是「課程」。
但仔細想想,如果這個課程還沒有老師教,學生怎麼選?
學生選的其實是「講授」——也就是「哪個老師教的哪個課程」。
例如:學生選的不是「三年級數學」,而是「David 老師的三年級數學班」。
決定要不要獨立成一張表
現在我們來決定每個個體要不要獨立成一張表。
科目和年級要不要獨立成一張表?
課程是由科目和年級組成的,例如「三年級數學」= 三年級 + 數學。
那我們需要另外建立「科目表」和「年級表」嗎?
在前一篇文章中,我們學到一個判斷原則:如果這個東西有「自己的性質」需要記錄,就應該獨立成一張表。
我們來檢查看看科目和年級有沒有額外的性質:
年級有什麼性質?
一年級、二年級、三年級… 好像就只有「幾年級」這個資訊,沒有其他要記錄的。
科目有什麼性質?
數學、英文、自然… 好像也只有「科目名稱」這個資訊,沒有其他要記錄的。
既然都沒有額外的性質,那就先不獨立成表,直接當作課程表裡的欄位就好:
| 課程 ID(PK) | 科目 | 年級 |
|---|---|---|
| 1 | 數學 | 三年級 |
| 2 | 英文 | 三年級 |
什麼時候要獨立成一張表?
如果有一天,老闆說:「我想把科目分類,數學是理科、歷史是文科。」
這時候科目就有自己的性質(分類)需要記錄,就應該把科目獨立成一張表:
| 科目 ID(PK) | 科目名稱 | 分類 |
|---|---|---|
| 1 | 數學 | 理科 |
| 2 | 歷史 | 文科 |
結論:目前先不獨立成表,等有需要再說。
教室要不要獨立成一張表?
教室有沒有自己的性質?
有的:
- 教室名稱(A101、A102…)
- 教室容量(最多可以坐幾個學生)
既然教室有這些性質需要記錄,就應該獨立成一張表。
據點要不要獨立成一張表?
題目說目前只有一個據點(一間補習班)。
如果只有一個據點,不獨立成表也沒關係,因為所有資料都屬於這個據點。
但如果老闆說:「我們以後可能會開分校。」
那就應該把據點獨立成一張表。而且,其他表都要加上「據點 ID」欄位:
- 老師屬於哪個據點?
- 學生屬於哪個據點?
- 教室屬於哪個據點?
這會增加設計的複雜度。所以如果要加這個彈性,記得跟老闆確認,也記得多收錢。
結論:看需求。目前只有一個據點,可以先不獨立成表。
設計表單結構
根據上面的討論,我們來設計表單。
人員表
老師和學生都是「人」,而且目前都只需要記錄姓名。
與其分成「老師表」和「學生表」,不如合併成一張「人員表」,用「身份」欄位區分。
| 人員 ID(PK) | 姓名 | 身份 |
|---|---|---|
| 1 | David | 老師 |
| 2 | Emily | 老師 |
| 3 | Frank | 學生 |
| 4 | Grace | 學生 |
如果以後老師有額外的性質(例如:可以教的領域),再把老師表拆出來。
課程表
課程由科目和年級組成。
| 課程 ID(PK) | 科目 | 年級 |
|---|---|---|
| 1 | 數學 | 三年級 |
| 2 | 英文 | 三年級 |
| 3 | 數學 | 二年級 |
教室表
| 教室 ID(PK) | 名稱 | 學生上限 |
|---|---|---|
| 1 | A101 | 30 |
| 2 | A102 | 25 |
| 3 | B201 | 40 |
講授表
講授是老師、課程、教室、時間的組合。
| 講授 ID(PK) | 課程 ID(FK) | 講師 ID(FK) | 教室 ID(FK) | 時間 |
|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 星期一 18:00 |
| 2 | 1 | 2 | 2 | 星期二 19:30 |
| 3 | 2 | 1 | 1 | 星期一 20:00 |
注意:同一個課程(三年級數學)可以有不同老師教,所以課程 ID = 1 出現了兩次,分別是 David 和 Emily 教的。
講授表的三個 ID 欄位都是外部鍵(FK):
- 課程 ID → 指向課程表的課程 ID
- 講師 ID → 指向人員表的人員 ID
- 教室 ID → 指向教室表的教室 ID
選課表
學生選的是「講授」,不是「課程」。
| 學生 ID(FK) | 講授 ID(FK) |
|---|---|
| 3 | 1 |
| 3 | 3 |
| 4 | 2 |
這張表的兩個欄位都是外部鍵:
- 學生 ID → 指向人員表的人員 ID
- 講授 ID → 指向講授表的講授 ID
這張表不需要額外的「選課 ID」嗎?
不一定需要。因為「學生 ID + 講授 ID」的組合本身就可以當作主鍵。
為什麼?因為同一個學生不會選同一個講授兩次。Frank(學生 ID = 3)選了 David 的三年級數學班(講授 ID = 1),他不可能再選一次。
所以「學生 ID = 3, 講授 ID = 1」這個組合在整張表裡只會出現一次,符合主鍵「不能重複」的條件。
這種用兩個欄位組合起來當主鍵的做法,叫做複合主鍵(Composite Primary Key)。
加上 PK、FK、NOT NULL 限制
現在我們來加上各種限制。
主鍵(PK)
所有的 ID 欄位都是主鍵:
- 人員表:人員 ID
- 課程表:課程 ID
- 教室表:教室 ID
- 講授表:講授 ID
NOT NULL
哪些欄位不能是空值?
- 人員表:姓名、身份都不應該是 NULL
- 課程表:科目、年級都不應該是 NULL
- 教室表:名稱不應該是 NULL,學生上限看情況
- 講授表:課程 ID、講師 ID、教室 ID、時間都不應該是 NULL
- 選課表:學生 ID、講授 ID 都不應該是 NULL
外部鍵(FK)
| 表單 | 欄位 | Reference(指向) |
|---|---|---|
| 講授表 | 課程 ID | 課程表.課程 ID |
| 講授表 | 講師 ID | 人員表.人員 ID |
| 講授表 | 教室 ID | 教室表.教室 ID |
| 選課表 | 學生 ID | 人員表.人員 ID |
| 選課表 | 講授 ID | 講授表.講授 ID |
「課程表.課程 ID」是什麼意思?
這是一種表示「哪張表的哪個欄位」的寫法。
格式是:表單名稱.欄位名稱
例如:
課程表.課程 ID= 課程表裡面的「課程 ID」欄位人員表.人員 ID= 人員表裡面的「人員 ID」欄位
所以「講授表的課程 ID 指向課程表.課程 ID」意思就是:講授表裡的「課程 ID」欄位,是一個外部鍵,它參考的對象是課程表裡的「課程 ID」欄位。
學生上限要放在哪裡?
這是一個有趣的問題。
學生上限可以放在很多地方:
- 放在教室表:按照教室容量決定(A101 最多 30 人)
- 放在課程表:按照課程決定(三年級數學最多 25 人)
- 放在講授表:按照講授決定(David 的三年級數學班最多 20 人)
哪個才對?
答案是:看你的業務邏輯。
- 如果學生上限只跟教室有關 → 放在教室表
- 如果學生上限跟課程有關 → 放在課程表
- 如果學生上限跟「誰教」也有關(David 老師喜歡小班教學)→ 放在講授表
- 如果以上都有關 → 可能每個表都要放,然後取最小值
這就是為什麼我們一直強調:沒有標準答案,要看情境。
加入據點表的設計
如果老闆說要開分校,我們就需要加入據點表。
據點表
| 據點 ID(PK) | 名稱 |
|---|---|
| 1 | 總校 |
| 2 | 信義分校 |
其他表要加上據點 ID
加入據點之後,老師、學生、教室都屬於某個據點。
人員表:
| 人員 ID(PK) | 姓名 | 身份 | 據點 ID(FK) |
|---|---|---|---|
| 1 | David | 老師 | 1 |
| 2 | Emily | 老師 | 2 |
教室表:
| 教室 ID(PK) | 名稱 | 學生上限 | 據點 ID(FK) |
|---|---|---|---|
| 1 | A101 | 30 | 1 |
| 2 | A102 | 25 | 2 |
課程要不要跟據點有關?不一定。如果所有分校教的課程都一樣,就不用關聯。如果有些分校只教特定課程(例如:自然組分校),那課程表也要加上據點 ID。
重點整理
這次的實戰練習,我們學到了幾件事:
設計資料庫的流程
- 找出個體:從需求中找出有哪些「東西」
- 找出關係:這些個體之間有什麼關係
- 決定拆表:根據正規化原則,決定要不要拆表
- 設計結構:設計每張表的欄位
- 加上限制:加上 PK、FK、NOT NULL 等限制
資料庫設計的核心觀念
- 沒有標準答案:資料庫設計要看你的業務邏輯和情境
- 彈性有成本:要保留彈性(例如:據點),就要多做設計,記得跟老闆多收錢
- 先簡單再複雜:不確定的時候,先不拆表,等有需要再拆
- 為業務邏輯服務:所有的設計都是為了讓業務邏輯更容易實現
設計資料庫的時候,記得把你的情境和考量寫清楚。這樣別人才能理解你為什麼這樣設計,也才能討論這樣設計合不合理。