Day6 先把 User Story 畫成 UI 草圖,Day7 再把按鈕背後的判斷整理成流程圖。走到這裡,會遇到一個很實際的問題:使用者建立的帳號、專案與 Task,到底要存在哪裡?如果程式關閉或伺服器重新啟動後,資料就跟著消失,前面設計的畫面和流程也無法運作。
資料庫會把需要長期保存的資料記錄下來,並在新增、查詢或修改時維持一致。這篇先認識關聯式資料庫的基本概念,下一篇再把它們套用到專案管理網站的 Schema 設計。
關聯式資料庫(Relational Database)會把資料分成一張張資料表,再用欄位描述每筆資料的內容。不同資料表也能透過共同的識別值建立關係。
以購物網站為例,系統通常會分開保存:
如果把這些內容全部塞進同一張表,會員姓名、Email 與商品資訊會隨著每筆訂單不斷重複。關聯式資料庫會依資料的用途拆成不同表格,需要時再把它們組合起來。
它的外觀看起來有點像 Excel,但用途不太一樣。Excel 適合人工整理與計算;關聯式資料庫則能定義資料型別、唯一性與表格之間的關係,也能讓多人或多個程式同時讀寫資料。
先看兩張表。第一張記錄會員:
| 會員編號 | 姓名 | |
|---|---|---|
| 1 | 小明 | ming@test.com |
| 2 | 小華 | hua@test.com |
| 3 | 小美 | mei@test.com |
第二張記錄訂單:
| 訂單編號 | 會員編號 | 金額 |
|---|---|---|
| 1001 | 1 | 500 |
| 1002 | 1 | 800 |
| 1003 | 2 | 1200 |
兩張表都有「會員編號」。訂單 1001 的會員編號是 1,回到會員表便能找到小明,因此可以判斷這張訂單屬於小明。
小明(會員編號 1)
├── 訂單 1001
└── 訂單 1002
小華(會員編號 2)
└── 訂單 1003
不過,「關聯式」這個名稱不只是指資料表彼此有關係。在關聯模型中,Relation 是一組結構相同的資料;初學時可以先近似理解成一張 Table,表內的每一列則是一筆資料。會員表與訂單表都是 Relation,再透過 Key 對應彼此的資料。
換句話說,資料先以表格結構保存,再利用共同欄位建立連結。程式因此能從訂單找到會員,也能查出某位會員的所有訂單。
可以先把關聯式資料庫想成公司的 Excel 活頁簿,每個 Sheet 各自保存一類資料。
| 員工編號 | 姓名 | 部門編號 |
|---|---|---|
| E001 | Louis | D01 |
| E002 | Amy | D02 |
| E003 | Kevin | D01 |
| 部門編號 | 部門名稱 |
|---|---|
| D01 | 資訊部 |
| D02 | 財務部 |
| 專案編號 | 專案名稱 | 負責人編號 |
|---|---|---|
| P001 | 網站改版 | E001 |
| P002 | ERP 導入 | E003 |
員工表中的 D01 可以對應到資訊部;專案表中的 E001 則對應到 Louis。沿著這些編號查找,就能回答「Louis 在哪個部門」或「網站改版由誰負責」。
網站改版(P001)
└── 負責人 E001
└── Louis
└── 部門 D01
└── 資訊部
表格只保存必要的編號,不需要在每個專案內重寫員工姓名與部門名稱。當 Louis 調到其他部門時,只要修改員工表的一筆資料,專案紀錄仍能指向同一位員工。
剛開始接觸資料庫,會先遇到 Table、Column 與 Row。以下用會員資料說明。
| MemberId | Name | Age | |
|---|---|---|---|
| 1 | Louis | louis@test.com | 30 |
| 2 | Amy | amy@test.com | 25 |
| 3 | Kevin | kevin@test.com | 32 |
Table 是保存同一類資料的表格,例如 Member 用來保存會員。實際系統通常會有多張資料表,各自負責帳號、專案或工作項目等資料。
Column 是表格直向的欄位,用來定義一筆資料要記錄哪些屬性。Member 表中的 MemberId、Name、Email 與 Age 都是 Column。
欄位除了名稱,也會設定資料型別與限制。例如 Age 可以使用整數型別,Email 可以限制長度;必要欄位則不允許空值。這些規則能在資料寫入時先擋下不合理的內容。
Row 是表格橫向的一筆紀錄。以下內容就是一位會員的完整資料:
1 | Louis | louis@test.com | 30
可以把三個名詞記成:
Table:保存哪一類資料
Column:這類資料有哪些屬性
Row:實際保存的一筆資料
同名的人很常見。假設會員表裡有三位王小明,只靠姓名無法確定要查詢或修改哪一筆資料。
| MemberId | Name |
|---|---|
| 1 | 王小明 |
| 2 | 王小明 |
| 3 | 王小明 |
Primary Key(主鍵,簡稱 PK)用來唯一識別一筆資料。上表的 MemberId 就是主鍵,因此 MemberId = 2 只會對應到其中一位會員。
主鍵必須符合兩個基本條件:
主鍵不一定只有一個欄位。有些資料會把兩個以上的欄位合在一起作為複合主鍵,例如選課紀錄可用 StudentId + CourseId 表示「某位學生選了某門課」。只要這組值能唯一識別一筆資料,就能作為主鍵。
把主鍵比喻成身分證字號很容易理解,但要留意:資料庫主鍵是系統用來辨識資料的值,不代表一定要存放真實身分證字號。實務上常使用自動產生的數字或 UUID,避免把具個資性質的欄位拿來當識別鍵。
Primary Key 可以辨識資料,Foreign Key(外鍵,簡稱 FK)則用來指向另一張表的資料。
| MemberId | Name |
|---|---|
| 1 | Louis |
| 2 | Amy |
在 Member 表中,MemberId 是 Primary Key。
| OrderId | MemberId | Amount |
|---|---|---|
| 1001 | 1 | 500 |
| 1002 | 1 | 1000 |
| 1003 | 2 | 300 |
在 Order 表中,OrderId 是 Primary Key,而 MemberId 是 Foreign Key。Order.MemberId 指向 Member.MemberId,所以 Order.MemberId = 1 代表這張訂單屬於 Louis。

外鍵不只是方便查詢,也能保護資料一致性。若會員表沒有 MemberId = 99,資料庫可以拒絕新增一張 MemberId = 99 的訂單,避免出現找不到會員的孤兒資料。
購物網站除了會員與訂單,還需要商品與訂單明細。這幾張表的責任不同:會員表保存誰下單,商品表保存販售內容,訂單表記錄一次交易,訂單明細則保存這次交易買了哪些商品。
| MemberId | Name |
|---|---|
| 1 | Louis |
| ProductId | ProductName | Price |
|---|---|---|
| 101 | 鍵盤 | 2000 |
| 102 | 滑鼠 | 1000 |
| OrderId | MemberId |
|---|---|
| 5001 | 1 |
| OrderId | ProductId | Quantity |
|---|---|---|
| 5001 | 101 | 1 |
| 5001 | 102 | 2 |
沿著這些關聯,訂單 5001 可以解讀為:Louis 購買一個鍵盤與兩個滑鼠。
Louis(MemberId 1)
└── Order 5001
├── 鍵盤(ProductId 101)× 1
└── 滑鼠(ProductId 102)× 2
OrderDetail 是 Order 與 Product 之間的中介表。它不只連接兩張表,還保存數量這個屬於交易的資訊。若還要保留成交時的單價,也可以在訂單明細加入 UnitPrice,因為商品日後調價時,舊訂單的成交金額不應跟著改變。
假設會員、訂單與商品全部放在一起:
| 訂單 | 會員姓名 | 商品 | 商品價格 | |
|---|---|---|---|---|
| 1001 | Louis | louis@test.com | 鍵盤 | 2000 |
| 1001 | Louis | louis@test.com | 滑鼠 | 1000 |
| 1002 | Louis | louis@test.com | 螢幕 | 8000 |
Louis 的姓名與 Email 在每筆商品紀錄中重複。當 Email 改成 new@test.com,所有相關資料都得一起修改;只要漏掉一筆,同一位會員便會出現兩個 Email。
把資料拆成 Member、Order 與 OrderDetail 後,會員資料只需要保存一次,訂單只記錄 MemberId。修改會員 Email 時,只會動到 Member 表中的一筆資料。
這種設計可以減少三類常見問題:
不過,拆表也不是越多越好。每張表都要有明確用途,並依資料生命週期與查詢方式決定是否拆分。下一篇會再談正規化與實際 Schema 設計。
資料表之間常見的關係有一對一、一對多與多對多。關係描述的是兩邊各自能對應幾筆資料。
會員 1 ───── 1 會員詳細資料
一位會員只對應一份詳細資料,一份詳細資料也只屬於一位會員。這類設計常用來把較少使用或有不同存取權限的資料分開保存。
會員 1 ───── N 訂單
一位會員可以有多張訂單,但每張訂單只屬於一位會員。這是很常見的關係,外鍵通常放在「多」的那一方,因此 Order 表會保存 MemberId。
學生與課程就是多對多:一位學生可以選多門課,一門課也能有多位學生。
關聯式資料庫通常會加入一張中介表,把多對多拆成兩個一對多:
Student 1 ── N Enrollment N ── 1 Course
Enrollment 可以使用 StudentId 與 CourseId 作為複合主鍵,避免同一位學生重複選同一門課。如果選課還要記錄學期或成績,這些欄位也會放在 Enrollment,因為它們描述的是「這次選課」,而不是學生或課程本身。
資料庫與 SQL 不是同一件事。關聯式資料庫負責保存及管理資料;SQL(Structured Query Language,結構化查詢語言)則是我們操作資料庫時使用的語言。
例如,查詢所有會員可以寫成:
SELECT *
FROM Member;
新增、修改與刪除資料,則會使用 INSERT、UPDATE 與 DELETE。這些指令描述「要對資料做什麼」,資料庫管理系統負責執行並維護資料。
常見的關聯式資料庫管理系統包括:
它們大多支援 SQL,但在資料型別、函式與部分語法上仍有差異。本系列的專案會使用 SQL Server,因此後續範例會以 SQL Server 的行為為準。
這個系列要開發的是專案管理網站,需要保存帳號、專案、專案成員與工作項目。依照前面的概念,可以先整理成四張表:

一個帳號可以參與多個專案,一個專案也有多位成員,因此需要 ProjectMembers 保存兩者的多對多關係。TaskItems.ProjectId 表示工作項目屬於哪個專案,AssignedAccountId 則記錄指派對象。實際寫入工作項目前,後端還要確認指派對象確實是該專案的成員。
回頭看 Day6 的 Task 清單,畫面上的標題、狀態、交付期限與指派對象,都要從資料庫讀取。Day7 流程中的版本衝突、軟刪除和 Email 寄送紀錄,也各自需要資料配合。上面四張表只是先抓出主要實體,還不是完整的 Schema。
這裡先看資料如何分組與連接,不急著決定所有欄位。真正設計 Schema 時,還要回頭確認 User Story、資料生命週期與查詢方式。例如工作項目清單上的 checkbox 只用於當次批次選取,重新整理後就會消失,因此不需要存進資料庫。下一篇會繼續處理這些判斷。
先記住以下六個名詞,就能掌握關聯式資料庫的基本結構:
| 名詞 | 白話意思 |
|---|---|
| Database | 管理多張資料表與相關規則的系統 |
| Table | 保存同一類資料的表格 |
| Column | 定義這類資料要記錄哪些屬性 |
| Row | 實際保存的一筆資料 |
| Primary Key | 唯一識別一筆資料的欄位或欄位組合 |
| Foreign Key | 指向另一張表資料的欄位 |
把它們放在一起,可以得到以下結構:
Database
│
├── Member
│ ├── PK MemberId
│ ├── Name
│ └── Email
│
└── Order
├── PK OrderId
├── FK MemberId ──────> Member.MemberId
└── Amount
關聯式資料庫會把不同用途的資料放進各自的表格,再透過 Primary Key 與 Foreign Key 建立關係。理解這個結構後,接下來學習 JOIN、正規化與 ER Model 時,就能知道每個工具是在解決什麼問題,而不是只記住一串名詞。
下一篇會回到 Project Management Web,從 User Story 找出 Entity、Attribute 與 Relationship,再把帳號、專案、成員和工作項目轉成實際的資料表設計。