MySQL の中間テーブル設計
多対多リレーションを実現するために使うのが中間テーブル(結合テーブル、ピボットテーブルとも呼ばれます)です。リレーショナルデータベースでは多対多を直接表現する方法がないため、2つの1対多リレーションに分解して中間テーブルで橋渡しします。
基本的な構造
ユーザーとロールの多対多関係を例にとります。1人のユーザーは複数のロールを持ち、1つのロールには複数のユーザーが所属できます。
-- 親テーブル
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
-- 中間テーブル
CREATE TABLE user_roles (
user_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);中間テーブルの主キーは user_id と role_id の複合主キーにするのが定石です。これにより、同じユーザーとロールの組み合わせが重複して登録されるのを防げます。
サロゲートキーを持たせるか
中間テーブルに独立した id カラム(サロゲートキー)を持たせるかどうかは設計の判断ポイントです。
シンプルで無駄がない。純粋に関連だけを管理する中間テーブルにはこれで十分。ORM によっては複合主キーの扱いが面倒になる場合がある。
ORM との相性がよい。中間テーブル自体を API で操作する場合に識別子として使える。中間テーブルに追加の属性を持たせる場合に管理しやすい。
中間テーブルに属性を持たせる
関連に付随する情報がある場合は、中間テーブルにカラムを追加します。
CREATE TABLE enrollments (
id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT NOT NULL,
enrolled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
grade CHAR(2) DEFAULT NULL,
UNIQUE KEY uk_student_course (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);この例では enrolled_at(登録日時)と grade(成績)が関連に付随する属性です。こうした属性がある場合は、サロゲートキーを主キーにして複合ユニーク制約を別途設定するパターンが管理しやすくなります。
インデックス設計
中間テーブルのクエリパフォーマンスは、インデックスの設計に大きく左右されます。
PRIMARY KEY (user_id, role_id) を定義すると、user_id での検索は高速ですが、role_id 単体での検索はインデックスが効きません。
role_id から検索するクエリ(特定ロールに所属するユーザー一覧)が必要な場合は、INDEX (role_id) を追加します。
CREATE TABLE user_roles (
user_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (user_id, role_id),
INDEX idx_role_id (role_id), -- 逆引き用
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);自己結合の中間テーブル
同じテーブル同士の多対多関係も中間テーブルで表現できます。SNS のフォロー関係が典型例です。
CREATE TABLE follows (
follower_id INT NOT NULL,
followee_id INT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (follower_id, followee_id),
FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (followee_id) REFERENCES users(id) ON DELETE CASCADE
);この場合、follower_id と followee_id はどちらも users テーブルを参照しますが、意味が異なります。カラム名で方向性を明確にすることで、クエリの可読性が格段に向上します。中間テーブルは多対多リレーションの要であり、正しく設計することでデータの整合性とクエリの効率を両立できます。



