リレーショナルDBの基本概念を図解で理解する

はじめに

Webアプリケーションやバックエンドの学習を進めると、必ずと言っていいほど「リレーショナルデータベース(RDB)」に出会います。MySQL、PostgreSQL、SQLite、Oracle、SQL Server などはすべて RDB の仲間です。

RDB は「データを表(テーブル)として持ち、表と表を関連(リレーション)でつなぐ」という一貫した考え方の上に成り立っています。本記事では、テーブル・主キー・外部キー・正規化・結合・トランザクション・インデックスといった基本概念を、図で整理しながら入門者向けに解説します。

そもそもリレーショナルとは何か

RDB の「リレーショナル(関係)」は、数学の集合と関係の理論に由来しますが、実務でまず押さえるべきは次のシンプルなイメージで十分です。

  • データは行(レコード)列(カラム)を持つ表(テーブル)で管理する
  • 表と表は共通の値を介してつながる

まずは1枚のテーブルを見てみましょう。

users テーブル
+----+----------+---------------------+
| id | name     | email               |
+----+----------+---------------------+
| 1  | 田中     | tanaka@example.com  |
| 2  | 佐藤     | sato@example.com    |
| 3  | 鈴木     | suzuki@example.com  |
+----+----------+---------------------+
  • カラム(列): id name email のような属性。列ごとに型が決まる
  • レコード(行): 「1人分のデータ」のような意味あるひとまとまり
  • テーブル: 同じ構造のレコードを集めたもの

主キー(Primary Key)— 行を一意に識別する

テーブルの中で「その1行を確実に特定できる列」を主キーと呼びます。上の例では id が主キーです。主キーは一意(重複しない)NOT NULL(空にできない)という性質を持ちます。

名前やメールは変わりうるし重複もしうるため、変わらない・重複しない番号を1本用意して「この行はこれ」と指し示せるようにします。実務では連番の id(サロゲートキー)を主キーにすることが多いです。

外部キー(Foreign Key)— テーブルをつなぐ

RDB の真価は、複数のテーブルを関連づけられる点にあります。その仕組みが外部キーです。

users                         orders
+----+--------+               +----+---------+--------+
| id | name   |               | id | user_id | amount |
+----+--------+               +----+---------+--------+
| 1  | 田中   | <──────┐      | 10 | 1       | 3000   |
| 2  | 佐藤   | <──┐   └───────| 11 | 1       | 1500   |
| 3  | 鈴木   |    └───────────| 12 | 2       | 8000   |
+----+--------+                +----+---------+--------+
  ↑ 主キー                            ↑ 外部キー(users.id を参照)

orders.user_idusers.id を指す外部キーです。これにより「どの注文が誰のものか」を辿れます。さらに外部キーには参照整合性の役割があり、存在しないユーザーの注文を作れないよう DB 側で防げます。

リレーションの種類 — 1対多と多対多

テーブル間の関係は主に3種類です。

【1対1】  users 1 ── 1 user_profiles
【1対多】  users 1 ──< orders     (最も多い)
【多対多】 students >──< courses

多対多はテーブルを直接つなげないため、中間テーブル(結合テーブル)を挟みます。

students          enrollments             courses
+----+------+     +------------+---------+ +----+------+
| id | name |     | student_id | course_id| | id | title|
+----+------+     +------------+---------+ +----+------+
| 1  | 田中 |<──  | 1          | 100     |─>| 100| 数学 |
| 2  | 佐藤 |<──  | 1          | 200     |─>| 200| 英語 |
+----+------+     | 2          | 100     |  +----+------+
                  +------------+---------+
                    ↑ 中間テーブル

「多対多を見たら中間テーブル」と覚えておくと設計で迷いません。

正規化 — 重複をなくして矛盾を防ぐ

初心者がやりがちなのが、1枚の巨大なテーブルに何でも詰め込む設計です。

悪い例: すべて1枚に
+----+----------+---------------------+--------+
| id | user_name| user_email          | amount |
+----+----------+---------------------+--------+
| 10 | 田中     | tanaka@example.com  | 3000   |
| 11 | 田中     | tanaka@example.com  | 1500   |  ← 重複
| 12 | 佐藤     | sato@example.com    | 8000   |
+----+----------+---------------------+--------+

この設計は更新異常(メール変更時に全行を直す必要がある)、無駄な重複挿入異常(注文の無いユーザーを登録できない)を招きます。

解決するのが正規化です。「1つの事実は1か所にだけ持つ」ように表を分割します。

users                       orders
+----+--------+----------+  +----+---------+--------+
| id | name   | email    |  | id | user_id | amount |
+----+--------+----------+  +----+---------+--------+
| 1  | 田中   | tanaka.. |  | 10 | 1       | 3000   |
| 2  | 佐藤   | sato..   |  | 11 | 1       | 1500   |
+----+--------+----------+  | 12 | 2       | 8000   |
                            +----+---------+--------+

入門者はまず「同じ事実を複数箇所に持たない」という原則を意識すれば十分です。性能のためにあえて重複を許す「非正規化」もありますが、まず正しく正規化できることが前提です。

テーブルを定義してみる(DDL)

CREATE TABLE users (
  id    INTEGER PRIMARY KEY,
  name  VARCHAR(50) NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE orders (
  id      INTEGER PRIMARY KEY,
  user_id INTEGER NOT NULL,
  amount  INTEGER NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

FOREIGN KEY ... REFERENCES により、参照整合性が DB レベルで守られます。

結合(JOIN)— つながりを使って取り出す

分割したテーブルは、JOIN で再びつなげて取り出します。

SELECT users.name, orders.amount
FROM orders
JOIN users ON orders.user_id = users.id
WHERE users.name = '田中';

JOIN の種類は入門段階では次の2つで十分です。

INNER JOIN : 両方に対応する行があるものだけ返す
LEFT JOIN  : 左側(users)を全部残す(注文が無い人も NULL で返る)

「注文したことがない人も含めたい」なら LEFT JOIN、というように目的で使い分けます。

トランザクション —「全部成功か、全部やり直し」

複数の更新を「ひとまとまり」として扱う仕組みがトランザクションです。送金が典型例です。

BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;   -- 途中で失敗すれば ROLLBACK; で全部なかったことにする

片方だけ成功してお金が消える、といった中途半端な状態を防ぎます。この信頼性をまとめて ACID(原子性・一貫性・独立性・永続性)と呼び、RDB が業務システムで選ばれ続ける大きな理由になっています。

インデックス — 検索を速くする索引

行数が増えると条件検索が遅くなります。全行を上から調べる(フルスキャン)代わりに、本の巻末索引のような早見表を持つのがインデックスです。

CREATE INDEX idx_users_email ON users(email);

ただし検索は速くなる一方、書き込みは索引更新分だけ遅くなり容量も増えます。よく検索する列に絞って付けるのが基本です。

全体像のまとめ図

テーブル(行 × 列)
  ├─ 主キー      : 行を一意に識別
  ├─ 外部キー    : 他テーブルを参照しつなぐ
  └─ インデックス: 検索を高速化する索引
リレーション(1対1 / 1対多 / 多対多 → 中間テーブル)
正規化    : 重複を排除し矛盾を防ぐ
JOIN      : 分けた表をつないで取り出す
トランザクション : 一連の更新をまとめて保証(ACID)

注意点

  • 主キーは必ず設ける
  • 外部キー制約は積極的に使い、データ破損を DB に防いでもらう
  • 正規化しすぎ・しなさすぎの両方に注意(まず正規化、性能問題は実測後に非正規化を検討)
  • インデックスは書き込みコストとのトレードオフを意識する
  • 型名や文法は製品ごとに異なるため、使う製品の公式ドキュメントも確認する

まとめ

  • RDB はデータをテーブルで持ち、主キーで行を識別し、外部キーでつなぐ
  • 関係は 1対多が基本、多対多は中間テーブルで表現する
  • 正規化で「1つの事実は1か所」を保ち、JOINで必要な形に組み立てる
  • トランザクション(ACID)が信頼できる更新を保証し、インデックスが検索を速くする

これらは製品を問わず共通の土台です。まずは SQLite で小さな表を作り、分けて・つないで、を手で試すのがおすすめです。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください