<CodeLearn/>
データベース レッスン3

リレーションと正規化

テーブル設計の原則と外部キーによる関連付けを学ぼう

主キーと外部キー

主キー(Primary Key)は、テーブル内の各レコードを一意に識別するカラムです。外部キー(Foreign Key)は、別のテーブルの主キーを参照するカラムで、 テーブル間の関連を定義します。

-- 主キー: id が各ユーザーを一意に識別
CREATE TABLE users (
  id    SERIAL PRIMARY KEY,  -- 主キー(自動採番)
  name  VARCHAR(100) NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL
);

-- 外部キー: user_id が users テーブルの id を参照
CREATE TABLE posts (
  id       SERIAL PRIMARY KEY,
  user_id  INTEGER NOT NULL,
  title    VARCHAR(200) NOT NULL,
  body     TEXT,

  -- 外部キー制約
  CONSTRAINT fk_user
    FOREIGN KEY (user_id)
    REFERENCES users(id)
    ON DELETE CASCADE  -- ユーザー削除時に投稿も削除
);

ON DELETE CASCADE

親レコード削除時に子レコードも自動削除

ON DELETE SET NULL

親レコード削除時に外部キーを NULL にする

リレーションの種類

テーブル間の関連には3つのパターンがあります。適切なリレーションを設計することがデータベース設計の要です。

1対1(One-to-One)

一方のレコードがもう一方の1件だけに対応する関係。

-- ユーザーとプロフィール(1対1)
CREATE TABLE users (
  id   SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

CREATE TABLE profiles (
  id      SERIAL PRIMARY KEY,
  user_id INTEGER UNIQUE NOT NULL REFERENCES users(id), -- UNIQUE で1対1を保証
  bio     TEXT,
  avatar  VARCHAR(500)
);

1対多(One-to-Many)

1つのレコードが複数のレコードに対応する、最もよく使う関係。

-- ユーザーと投稿(1対多)
-- 1人のユーザーが複数の投稿を持てる
CREATE TABLE posts (
  id      SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id), -- 外部キー(UNIQUEなし)
  title   VARCHAR(200) NOT NULL
);

-- users(1) ←→ posts(多)
-- user_id = 1 のレコードが posts に複数存在できる

多対多(Many-to-Many)

両方のテーブルが互いに複数の関連を持つ。中間テーブルが必要。

-- 投稿とタグ(多対多)
CREATE TABLE tags (
  id   SERIAL PRIMARY KEY,
  name VARCHAR(50) UNIQUE NOT NULL
);

-- 中間テーブル(結合テーブル)
CREATE TABLE post_tags (
  post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
  tag_id  INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
  PRIMARY KEY (post_id, tag_id)  -- 複合主キー
);

-- 投稿にタグを紐づける
INSERT INTO post_tags (post_id, tag_id) VALUES (1, 1), (1, 2), (2, 1);

-- 特定の投稿のタグ一覧
SELECT t.name
FROM tags t
INNER JOIN post_tags pt ON t.id = pt.tag_id
WHERE pt.post_id = 1;

正規化(Normalization)

正規化とは、データの重複を排除し、整合性を保つためにテーブルを分割する設計手法です。 一般的に第3正規形(3NF)までを目指します。

第1正規形(1NF)

各カラムに1つの値だけを持たせる(繰り返しグループを排除)

-- 悪い例(1NFに違反): カンマ区切りで複数の値
| id | name     | phones                    |
|----|----------|---------------------------|
| 1  | 田中太郎 | 090-1111-2222,03-3333-4444|

-- 良い例(1NF準拠): 1カラム1値
| id | name     | phone          |
|----|----------|----------------|
| 1  | 田中太郎 | 090-1111-2222  |
| 1  | 田中太郎 | 03-3333-4444   |

第2正規形(2NF)

部分関数従属を排除する(主キーの一部だけに依存するカラムを分離)

-- 悪い例: student_name は student_id だけで決まる
| student_id | course_id | student_name | grade |
|------------|-----------|--------------|-------|
| 1          | 101       | 田中太郎     | A     |

-- 良い例: テーブルを分割
-- students: | student_id | student_name |
-- grades:   | student_id | course_id | grade |

第3正規形(3NF)

推移的関数従属を排除する(主キー以外のカラムに依存するカラムを分離)

-- 悪い例: department_name は department_id に依存
| employee_id | department_id | department_name |
|-------------|---------------|-----------------|
| 1           | 10            | 開発部          |

-- 良い例: 部署テーブルを分離
-- employees:   | employee_id | department_id |
-- departments: | department_id | department_name |

ER図(Entity-Relationship Diagram)

ER図はテーブル間の関係を視覚的に表現する図です。 テーブル設計の前に ER 図を書くと、全体の構造を把握しやすくなります。

ブログシステムの ER 図(テキスト表現)

┌──────────────┐       ┌──────────────┐       ┌──────────────┐
│   users      │       │   posts      │       │   tags       │
├──────────────┤       ├──────────────┤       ├──────────────┤
│ id (PK)      │──1:N──│ id (PK)      │──N:N──│ id (PK)      │
│ name         │       │ user_id (FK) │       │ name         │
│ email        │       │ title        │       └──────────────┘
│ created_at   │       │ body         │              │
└──────────────┘       │ published    │       ┌──────────────┐
       │               │ created_at   │       │  post_tags   │
       │               └──────────────┘       ├──────────────┤
       │                      │               │ post_id (FK) │
       │               ┌──────────────┐       │ tag_id (FK)  │
       └──────1:1──────│  profiles    │       └──────────────┘
                       ├──────────────┤
                       │ id (PK)      │       ┌──────────────┐
                       │ user_id (FK) │       │  comments    │
                       │ bio          │       ├──────────────┤
                       │ avatar       │       │ id (PK)      │
                       └──────────────┘       │ post_id (FK) │
                                              │ user_id (FK) │
                                              │ body         │
                                              └──────────────┘

実務では dbdiagram.iodraw.io などのツールで ER 図を作成します。設計段階でチームメンバーと共有し、合意を取ることが重要です。

テーブル設計のベストプラクティス

命名規則を統一する

テーブル名は複数形(users、posts)、カラム名は snake_case(created_at、user_id)で統一

必ず主キーを設定する

自動採番の id カラムを主キーにするのが一般的。UUID を使う場合もある

created_at / updated_at を入れる

レコードの作成日時・更新日時を記録しておくとデバッグや分析に便利

適切な制約を設定する

NOT NULL、UNIQUE、外部キー制約で不正データの挿入を防ぐ

インデックスを適切に設定する

頻繁に検索・結合するカラムにはインデックスを張って高速化する

まとめ

  • 主キーはレコードを一意に識別し、外部キーはテーブル間の関連を定義する
  • リレーションは 1:1、1:N、N:N の3種類。N:N は中間テーブルで実現する
  • 正規化(1NF〜3NF)でデータの重複を排除し、整合性を保つ
  • ER 図でテーブル設計を視覚化してからコードを書き始める
  • 命名規則、制約、インデックスの設定がテーブル設計の品質を左右する