「多對多」關聯就是一個資料表裡的一筆資料可以跟另一個資料表的多筆資料有關聯,反過來也一樣。舉例來說: - 一個學生(在 students 表)可以選修多門課(在 courses 表)。 - 一門課也可以有很多學生選修。
問題來了,這種關聯如果直接存會很麻煩。所以我們會用到 中介表 來存這些關聯。
生活中的例子
想像一下,你有一個學生表跟一個課程表。如果你想在每個表裡加一個欄位來存所有關聯的資料,場面會超混亂:
- 在
students表裡,你得存學生選了哪些課。那要怎麼存?用逗號分隔?還是用陣列?查詢起來超麻煩。 - 在
courses表裡,你得存有哪些學生選這門課,這更讓人頭痛。
所以正確的做法是:建立第三個表,專門存學生跟課程之間的關聯。
中介表:我們的救星!
中介表(有時候也叫關聯表)解決了所有問題。它會有 兩個外鍵:
- 一個外鍵指向
students表。 - 一個外鍵指向
courses表。
這個表裡的每一筆資料,就是一個學生跟一門課的關聯。
建立「多對多」關聯的資料表
來實作一下!這裡是學生跟課程之間關聯的資料表建立方式:
步驟 1:建立 students 表
這是我們的學生表。這裡會存學生的唯一 ID 跟名字。
CREATE TABLE students (
student_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
student_id— 學生的唯一識別碼(自動遞增,感謝SERIAL!)。name— 學生名字。
步驟 2:建立 courses 表
現在來建立課程表。這裡會存課程的唯一 ID 跟名稱。
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY,
title TEXT NOT NULL
);
course_id— 課程的唯一識別碼。title— 課程名稱。
步驟 3:建立中介表 enrollments
現在來建立我們的魔法關聯表。它有兩個欄位,每個都是外鍵,分別指向對應的表。
CREATE TABLE enrollments (
student_id INT REFERENCES students(student_id),
course_id INT REFERENCES courses(course_id),
PRIMARY KEY (student_id, course_id)
);
來拆解一下結構:
student_id— 外鍵,指向students表的student_id。course_id— 外鍵,指向courses表的course_id。PRIMARY KEY (student_id, course_id)— 主鍵是這兩個外鍵的組合,這樣每個關聯都不會重複。
插入資料
來加點資料測試一下怎麼運作。
步驟 1:新增學生
INSERT INTO students (name) VALUES
('Alice'),
('Bob'),
('Charlie');
結果:
| student_id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
步驟 2:新增課程
INSERT INTO courses (title) VALUES
('Mathematics'),
('History'),
('Biology');
結果:
| course_id | title |
|---|---|
| 1 | Mathematics |
| 2 | History |
| 3 | Biology |
步驟 3:新增 enrollments 的資料
現在來讓學生選課。比如說:
Alice選了Mathematics跟History。Bob只選了Biology。Charlie三門課都選了。
INSERT INTO enrollments (student_id, course_id) VALUES
(1, 1), -- Alice 選 Mathematics
(1, 2), -- Alice 選 History
(2, 3), -- Bob 選 Biology
(3, 1), -- Charlie 選 Mathematics
(3, 2), -- Charlie 選 History
(3, 3); -- Charlie 選 Biology
結果:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
「多對多」關聯的查詢
現在有資料了,來看看怎麼查!
怎麼查一個學生選了哪些課?
比如說,要查 Alice(ID = 1)選了哪些課,可以這樣查:
SELECT c.title
FROM courses c
JOIN enrollments e ON c.course_id = e.course_id
WHERE e.student_id = 1;
結果:
| title |
|---|
| Mathematics |
| History |
怎麼查一門課有哪些學生選?
假設你想知道誰選了 Mathematics(ID = 1):
SELECT s.name
FROM students s
JOIN enrollments e ON s.student_id = e.student_id
WHERE e.course_id = 1;
結果:
| name |
|---|
| Alice |
| Charlie |
怎麼查所有學生跟他們選的課?
想要一次看到誰選了哪些課,可以這樣查:
SELECT s.name AS student, c.title AS course
FROM students s
JOIN enrollments e ON s.student_id = e.student_id
JOIN courses c ON e.course_id = c.course_id;
結果:
| student | course |
|---|---|
| Alice | Mathematics |
| Alice | History |
| Bob | Biology |
| Charlie | Mathematics |
| Charlie | History |
| Charlie | Biology |
enrollments 這個表讓我們的 schema 超彈性——你可以隨時加或刪學生跟課程的關聯,完全不用動到主表。用 JOIN 查詢也很方便,像是誰選了哪門課都能輕鬆查到。外鍵還會自動幫你檢查資料正確性——比如說,不會有人不小心選到根本不存在的課。
「多對多」關聯常見錯誤
沒有設唯一限制:如果沒設 PRIMARY KEY,一樣的關聯可能會被加好幾次。
資料完整性出錯:如果你插入一個不存在的 student_id 或 course_id,就會出錯。
刪資料順序錯誤:如果你先把 courses 裡的課刪掉,enrollments 裡的資料就會變成「孤兒」。要避免這種情況,記得在外鍵定義裡加 ON DELETE CASCADE。
GO TO FULL VERSION