CodeGym /コース /SQL SELF /中間テーブルを使ったMANY-TO-MANYリレーションのモデリング

中間テーブルを使ったMANY-TO-MANYリレーションのモデリング

SQL SELF
レベル 19 , レッスン 4
使用可能

「多対多」リレーションっていうのは、あるテーブルの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_idstudentsテーブルのstudent_idへの外部キー。
  • course_idcoursesテーブルの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にレコードを追加

今度は学生をコースに登録しよう。例えば:

  • AliceMathematicsHistoryに登録。
  • BobBiologyだけ登録。
  • 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_idcourse_idでレコードを追加しようとするとエラーになる。

削除順序のミス:先にcoursesからコースを消すと、enrollmentsに「孤児」レコードが残る。これを防ぐには、外部キー定義でON DELETE CASCADEを使おう。

1
アンケート/クイズ
外部キー、レベル 19、レッスン 4
使用不可
外部キー
外部キー
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION