リレーションシップとカーディナリティ
リレーションシップとカーディナリティ とは
データベースのリレーションシップとカーディナリティの概念について解説します。本レッスンでは、リレーションシップとカーディナリティ の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
なぜ重要か
テーブル同士の 関連(リレーションシップ) をどう表現するかは、データモデリングの肝です。そして、関連を語るときに最も大切なのが カーディナリティ(多重度) という概念。「1 対 1 なのか、1対多 なのか、多対多 なのか」が決まらないと、外部キー をどちらのテーブルに置くか、別途 中間テーブル を用意するかどうかが決まりません。逆に言えば、カーディナリティをはっきりさせるだけで、テーブル構造は半自動的に決まります。本レッスンでは、カーディナリティの読み方・書き方・SQL への落とし方を体系的に学びます。
カーディナリティの 4 パターン
カーディナリティは、関連の 両側でそれぞれ「何個と対応するか」 を表します。よく出てくる組み合わせは次の通りです。
| カーディナリティ | 例 | 物理的な実現方法 |
|---|---|---|
| 1 対 1 | ユーザ ↔ プロフィール | どちらかのテーブルに FK + UNIQUE |
| 1 対多 | ユーザ → 注文 | 多側に FK を置く |
| 多対多 | 学生 ↔ 講座 | 中間テーブルを作る |
| 0 または 1 対多 | チーム → メンバー (任意) | 多側 FK を NULL 許可にする |
任意 / 必須の区別
もう一つ重要なのが、最小カーディナリティ(必須か任意か)。例えば「注文は必ず 1 人のユーザに紐づく」のか「ユーザなしの注文も許す」のかで、NOT NULL 制約が変わります。ER 図ではカラスの足の根本の記号で表します。
||(縦棒2本) — ちょうど 1|o(棒+丸) — 0 または 1}o(足+丸) — 0 以上}|(足+棒) — 1 以上
ステップごとの動作
下図は典型的な 1対多 のカーディナリティの読み方です。USERS ||--o{ ORDERS は、「ユーザ 1 人は注文を 0 件以上持つ」「注文は必ず 1 人のユーザに属する」という意味になります。
この関係を物理モデルに落とすと、注文(多側)テーブルに user_id INT NOT NULL を置きます。NOT NULL は「注文は必ずユーザに属する」を表現するために必須です。逆に、これを NULL 許可にすると「ユーザなしの注文」がデータ上は許されてしまいます。
代表例
業務ルール「1 人のユーザは 0 件以上の注文を持つ」「各注文は 1 人のユーザに必ず属する」を SQL で表現します。
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 クエリ
SELECT
u.name,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total), 0) AS total_amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY u.id;LEFT JOIN + COALESCE を組み合わせることで、注文 0 件のユーザも 0 として表現できます。これが「ユーザ側は 0 個もあり得る」というカーディナリティの SQL 表現です。
カーディナリティから外部キーの位置を決める
カーディナリティが決まれば、外部キーをどこに置くかは機械的に決まります。
SQL クエリ
-- 1 対 1: どちらか一方に FK + UNIQUE
CREATE TABLE profiles (
user_id INT PRIMARY KEY,
bio TEXT
);
-- 1 対多: 多側に FK
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT NOT NULL
);
-- 多対多: 中間テーブルに両方の FK
CREATE TABLE student_courses (
student_id INT NOT NULL,
course_id INT NOT NULL,
PRIMARY KEY (student_id, course_id)
);よくある落とし穴
「
多対多」を見つけたら必ず中間テーブルを作る。両側のテーブルにカンマ区切り文字列で ID リストを持たせるのは絶対 NG。検索もできず、整合性も壊れます。中間テーブルは追加のコストではなく、必要な設計です。
最小カーディナリティ(必須か任意か)の判断は業務担当に確認する。「絶対に空にならないか?」を慎重に確認してから NOT NULL を決めること。後から NOT NULL を外すのは比較的容易ですが、NULL データが混ざってから NOT NULL を付けるのは難しい作業になります。
まとめ
- カーディナリティは「最大」(1 か N か)と「最小」(必須か任意か)の 2 軸で表す
- 1 対多は多側に FK、多対多は中間テーブル、1 対 1 はどちらかに FK + UNIQUE
- 必須 / 任意は NOT NULL の付け方として物理モデルに表れる
- 多対多を ID 文字列列で持たせるのは破綻パターン
次のレッスン
次は 1対1/1対多/多対多の設計 です。データベース設計における1対1、1対多、多対多のリレーションを学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- カーディナリティ の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. カーディナリティ とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
テーブル構造
schema.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
);
INSERT INTO users (id, name) VALUES
(1, '田中'),
(2, '鈴木'),
(3, '佐藤'),
(4, '山田');
INSERT INTO orders (id, user_id, total) VALUES
(1, 1, 1000),
(2, 1, 2500),
(3, 2, 800),
(4, 2, 1200),
(5, 2, 600),
(6, 4, 3000);期待される出力
| name | order_count | total_amount |
|---|---|---|
| 田中 | 2 | 3500 |
| 鈴木 | 3 | 2600 |
| 佐藤 | 0 | 0 |
| 山田 | 1 | 3000 |