リレーションシップとカーディナリティ

リレーションシップとカーディナリティ とは

データベースのリレーションシップとカーディナリティの概念について解説します。本レッスンでは、リレーションシップとカーディナリティ の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

なぜ重要か

テーブル同士の 関連(リレーションシップ をどう表現するかは、データモデリングの肝です。そして、関連を語るときに最も大切なのが カーディナリティ(多重度) という概念。「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 人のユーザに属する」という意味になります。

diagram (will load when visible)

この関係を物理モデルに落とすと、注文(多側)テーブルに 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 つができることを確認しましょう。

  1. カーディナリティ の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  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);

期待される出力

nameorder_counttotal_amount
田中23500
鈴木32600
佐藤00
山田13000

ヒント

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