1対1/1対多/多対多の設計

1対1/1対多/多対多の設計 とは

データベース設計における1対1、1対多、多対多のリレーションを学びます。本レッスンでは、1対1/1対多/多対多の設計 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

なぜ重要か

前レッスンでカーディナリティの基本を学びましたが、実務で最も悩むのは 「具体的にどのパターンを選ぶか」「物理的にどう実装するか」 です。同じ業務でも、1 対 1 として設計するか、1 つの大きなテーブルにまとめるか、選択肢があります。多対多中間テーブル で実装しますが、その中間テーブルにも追加 属性 を持たせるべきか否かなど、判断ポイントは多数あります。本レッスンでは、3 つのパターンそれぞれの 適用条件・物理設計パターン・SQL での操作方法 を体系的に学びます。

3 つのパターン比較

3 つの関連パターンの典型的な使い分けは以下の通りです。

パターン業務例物理設計使いどころ
1 対 1ユーザ ↔ プロフィール詳細どちらかに FK + UNIQUE列が多すぎる時の分割、または任意拡張
1 対多ユーザ → 注文多側に FK最も一般的なパターン
多対多学生 ↔ 講座中間テーブル両側から複数指定できる場合

ステップごとの動作

下図は 3 つのパターンを 1 枚にまとめた ER図 です。各エンティティの位置と FK の位置に注目してください。

diagram (will load when visible)

ポイントは次の通りです。

  • 1 対 1 は profiles.user_id主キー かつ 外部キー にしてしまうことで、UNIQUE 制約を兼ねる
  • 1対多 は多側の orders.user_id に FK
  • 多対多は中間テーブル enrollments に両側の FK を入れ、(student_id, course_id)複合主キー にする

代表例

1 対多 の SQL

最も頻出のパターン。注文の集計は LEFT JOIN で書きます。

SQL クエリ

CREATE TABLE users ( id INT PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL );

多対多 の SQL

中間テーブル enrollments を経由して JOIN します。「各学生が履修している講座数」を出すクエリは次のように書きます。

SQL クエリ

SELECT s.name, COUNT(e.course_id) AS course_count FROM students s LEFT JOIN enrollments e ON e.student_id = s.id GROUP BY s.id, s.name ORDER BY s.id;

多対多 + 関連属性

中間テーブルに追加属性を持たせることもよくあります。たとえば「履修日」「成績」など、関連自体の属性です。これは中間テーブルが 独自のエンティティに昇格 したと考えると分かりやすいです。

SQL クエリ

CREATE TABLE enrollments ( student_id INT NOT NULL, course_id INT NOT NULL, enrolled_at DATE NOT NULL, grade TEXT, PRIMARY KEY (student_id, course_id) );

1 対 1 の使いどころ

1 対 1 は意外と使い道が限られます。多くの場合「同じテーブルにまとめてもよい」ためです。それでも 1 対 1 を選ぶ典型ケースは次の通りです。

  • 任意の拡張情報 — プロフィールの追加項目(プロフィール画像、SNS リンクなど)
  • 大きな列の分離 — BLOB / TEXT 型の本文を別テーブルにして、メイン検索を速くする
  • 権限分離 — 機密情報(マイナンバー等)を別テーブルにして、別の権限で守る

よくある落とし穴

多対多の中間テーブルにも主キーを設計する。中間テーブルでは (left_id, right_id)複合主キー にすると、同じペアが二重登録されない保証になります。サロゲートキーだけだと、複数登録されても気づきません。

「1 対多」と思っていた関係が、業務拡張で「多対多」になる場合がある。例: 当初は「1 つの注文は 1 つの配送先」だったが、複数配送先対応で「1 注文に複数配送先」になる。設計段階で「将来の変化」も少しは考慮しておくと、後の手戻りが減ります。

まとめ

  • 1 対 1 はあまり使わず、使う時は分離理由(拡張・分離・権限)を明確にする
  • 1 対多が最も一般的で、多側に FK を置く
  • 多対多は中間テーブル + 複合主キーで実装する
  • 中間テーブルに属性が付くなら、独自エンティティに昇格させて考える

次のレッスン

次は モデリング基礎クイズ です。データベースモデリングの基礎知識をクイズ形式で確認し、理解を深めます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 1対1/1対多/多対多 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 1対1/1対多/多対多 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

テーブル構造

schema.sql

CREATE TABLE students ( id INT PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE courses ( id INT PRIMARY KEY, title TEXT NOT NULL ); CREATE TABLE enrollments ( student_id INT NOT NULL, course_id INT NOT NULL, enrolled_at DATE NOT NULL, grade TEXT, PRIMARY KEY (student_id, course_id) ); INSERT INTO students (id, name) VALUES (1, '田中'), (2, '鈴木'), (3, '佐藤'); INSERT INTO courses (id, title) VALUES (1, 'データベース基礎'), (2, 'アルゴリズム'), (3, 'Web開発'); INSERT INTO enrollments (student_id, course_id, enrolled_at, grade) VALUES (1, 1, '2026-04-01', 'A'), (1, 2, '2026-04-02', 'B'), (1, 3, '2026-04-03', 'A'), (2, 1, '2026-04-01', 'B'), (2, 2, '2026-04-02', 'C'), (3, 3, '2026-04-03', NULL);

期待される出力

student_namecourse_titlegrade
田中データベース基礎A
田中アルゴリズムB
田中Web開発A
鈴木データベース基礎B
鈴木アルゴリズムC
佐藤Web開発NULL

ヒント

query.sql
query.sql
学習モード
コードの実行結果
データベースを初期化中...