「多対多」リレーションっていうのは、あるテーブルの1つのレコードが別のテーブルの複数のレコードと関連できて、その逆もOKなやつ。例えば: - 1人の学生(studentsテーブル)は複数のコース(coursesテーブル)を受講できる。 - 1つのコースには複数の学生が参加できる。
でも、こういうリレーションを直接テーブルに持たせるのはめっちゃ面倒。そこで登場するのが中間テーブル。このテーブルがリレーションを管理してくれるんだ。
リアルな例で考えてみよう
学生テーブルとコーステーブルを作ったとする。もし各テーブルに関連データ全部を保存しようとしたら、カオスになるよ:
studentsテーブルに、学生が受講してるコースのリストを保存しなきゃいけない。でもリストってどうやって保存する?カンマ区切り?配列?クエリが超ややこしくなる。coursesテーブルに学生のリストを持たせるのも、頭が痛くなるだけ。
だから正解は、学生とコースのリレーションを保存する第3のテーブルを作ること!
中間テーブル:救世主!
中間テーブル(リレーションテーブルとも呼ばれる)は全部解決してくれる。2つの外部キーを持ってるんだ:
studentsテーブルへの外部キー。coursesテーブルへの外部キー。
このテーブルの1レコードが、特定の学生と特定のコースのリレーションを表してる。
「多対多」リレーション用テーブルの作り方
じゃあ実際にやってみよう!学生とコースのリレーション用テーブルを作る方法はこんな感じ:
ステップ1:studentsテーブルを作る
これが学生用テーブル。学生のユニークIDと名前を保存するよ。
CREATE TABLE students (
student_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
student_id— 学生のユニークID(自動増分、SERIALに感謝!)。name— 学生の名前。
ステップ2:coursesテーブルを作る
次はコース用テーブル。コースのユニークIDとタイトルを保存する。
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY,
title TEXT NOT NULL
);
course_id— コースのユニークID。title— コース名。
ステップ3:中間テーブルenrollmentsを作る
いよいよ魔法のリレーションテーブルを作るよ。2つのカラムがあって、それぞれ対応するテーブルへの外部キーになってる。
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)— 2つの外部キーの組み合わせが主キー。これでリレーションがユニークになる。
データの挿入
ちょっとデータを入れて、ちゃんと動くか見てみよう。
ステップ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は3つ全部のコースに登録。
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テーブルのおかげでスキーマが超柔軟になる。学生とコースのリレーションを簡単に追加・削除できるし、メインテーブルはそのまま。JOINクエリで誰がどのコースかもすぐ分かる。外部キーがあるから、存在しないコースに学生を登録しちゃうみたいなミスも自動で防げるよ。
「多対多」リレーションでありがちなミス
ユニーク制約がない:PRIMARY KEYを設定しないと、同じリレーションを何回も追加できちゃう。
データ整合性の破壊:存在しないstudent_idやcourse_idでレコードを追加しようとするとエラーになる。
削除順序のミス:先にcoursesからコースを消すと、enrollmentsに「孤児」レコードが残る。これを防ぐには、外部キー定義でON DELETE CASCADEを使おう。
GO TO FULL VERSION