はじめに
前回の「データベース設計入門 第3回:テーブル・カラム設計の基本」では、
- テーブル
- カラム
- レコード
- 主キー
- NULL
- データ型
- ID
など、テーブルを設計するうえでの基本について解説しました。
例えば、ECサイトであれば、
users
products
orders
のように、役割ごとにテーブルを分けてデータを管理します。
しかし、実際のシステムでは、それぞれのテーブルが完全に独立しているわけではありません。
例えば、
「この注文は、どのユーザーが行った注文なのか」
という情報が必要になります。
また、
「この注文には、どの商品が含まれているのか」
という情報も必要です。
つまり、テーブルを作ったあとは、
テーブル同士がどのような関係になっているのか
を考える必要があります。
このようなテーブル同士の関係を設計することを、一般にリレーション設計と呼びます。
今回は、テーブル同士の基本的な関係である、
- 1対1
- 1対多
- 多対多
について、ECサイトを例にしながら説明します。
また、テーブル同士を関連付けるために重要となる、
- 外部キー
- 親テーブルと子テーブル
- 中間テーブル
についても見ていきます。
この記事の対象者
この記事は、次のような人を対象にしています。
- エンジニアとして開発をしているが、データベースについてあまり詳しくない人
- SQLを書いた経験はあるものの、テーブル設計の経験が少ない人
- テーブル同士をどのように関連付ければよいのか分からない人
- 「1対多」「多対多」といった言葉を聞いたことはあるが、意味をよく理解していない人
- データベース設計を体系的に学び直したい人
SQLの基本的な知識があると理解しやすいですが、リレーション設計の経験がなくても問題ありません。
テーブル同士の「関係」とは
まずは、ユーザー情報を管理するusersテーブルと、注文情報を管理するordersテーブルを考えてみます。
users
-------------------
id
name
email
created_at
orders
-------------------
id
user_id
ordered_at
total_amount
例えば、次のようなユーザーがいるとします。
| id | name | |
|---|---|---|
| 1 | 山田太郎 | taro@example.com |
| 2 | 佐藤花子 | hanako@example.com |
そして、注文データが次のようになっているとします。
| id | user_id | ordered_at |
|---|---|---|
| 101 | 1 | 2026-09-01 |
| 102 | 1 | 2026-09-05 |
| 103 | 2 | 2026-09-07 |
ここで、ordersテーブルの
user_id
に注目してください。
注文ID「101」のuser_idは「1」になっています。
これは、
users.id = 1
のユーザー、つまり山田太郎さんが行った注文であることを表しています。
図にすると、次のようなイメージです。
users
id = 1 山田太郎
↑
│
│ user_id = 1
│
orders
id = 101
このように、あるテーブルのデータを別のテーブルから参照することで、テーブル同士の関係を表現できます。
外部キーとは
前回の記事では、レコードを一意に識別するための**主キー(PRIMARY KEY)**について説明しました。
例えばusersテーブルでは、
users.id
を主キーとして利用できます。
そして、先ほどのordersテーブルでは、
orders.user_id
から、
users.id
を参照していました。
users
-------------------
id ← 主キー
name
email
↑
│
│ 参照
│
orders
-------------------
id
user_id ← users.id を参照
ordered_at
このように、別のテーブルのキーを参照するために設定する制約を、
外部キー(FOREIGN KEY)
と呼びます。
外部キーを設定することで、
存在しないユーザーに対する注文
のような不整合をデータベース側で防ぐことができます。
例えば、usersテーブルに、
id = 1
id = 2
のユーザーしか存在しないとします。
その状態で、
orders.user_id = 999
という注文が登録されてしまうと、
「user_id = 999とは誰なのか?」
という状態になります。
適切に外部キー制約が設定されていれば、このような存在しないデータへの参照を防ぐことができます。
なお、
user_id
というカラムそのものを「外部キー」と呼ぶこともありますが、厳密には、そのカラムに対して設定される参照のルールが外部キー制約です。
テーブル同士の関係は大きく3種類ある
テーブル同士の基本的な関係には、次の3種類があります。
1対1
1対多
多対多
英語では、それぞれ、
1対1 → One-to-One
1対多 → One-to-Many
多対多 → Many-to-Many
と呼ばれます。
順番に見ていきましょう。
1対1
1対1とは、
ある1件のデータに対して、別のテーブルのデータが最大1件対応する関係
です。
例えば、ユーザーの基本情報とプロフィール情報を別々のテーブルで管理するとします。
ユーザー
│
│ 1
│
│ 1
プロフィール
テーブルとしては、例えば次のようになります。
users
-------------------
id
name
email
created_at
user_profiles
-------------------
id
user_id
birthday
phone_number
address
1人のユーザーに対して、プロフィールを1件だけ持てる設計です。
例えば、
| id | name |
|---|---|
| 1 | 山田太郎 |
| 2 | 佐藤花子 |
というユーザーがいるとします。
プロフィールは、
| id | user_id | birthday |
|---|---|---|
| 1 | 1 | 1990-01-01 |
| 2 | 2 | 1995-05-10 |
となります。
users.id = 1
に対応するプロフィールは、
user_profiles.user_id = 1
の1件です。
「1件だけ」を保証する必要がある
ここで注意したいのが、
user_profiles.user_id
を作っただけでは、必ずしも1対1になるわけではないということです。
例えば、
user_id = 1
user_id = 1
user_id = 1
という3件のプロフィールを登録できてしまえば、1対多になってしまいます。
1ユーザーにつきプロフィールを1件だけにしたいのであれば、
user_profiles.user_id
に重複を許さない制約を設定するなどして、
同じユーザーに対するプロフィールは1件だけ
というルールをデータベース側でも保証する必要があります。
1対1なら必ずテーブルを分けるのか
ここで、
「1対1なら、最初から全部usersテーブルに入れてもよいのでは?」
と思うかもしれません。
その考え方も間違いではありません。
例えば、
users
-------------------
id
name
email
birthday
phone_number
address
としても設計できます。
そのため、
1対1の関係があるから必ずテーブルを分割する
というわけではありません。
例えば、
- 普段は利用しない情報を分離したい
- 特定機能でだけ使用する情報を分けたい
- セキュリティや権限の都合で分離したい
- テーブルごとの役割を明確にしたい
といった理由がある場合に、別テーブルにすることがあります。
重要なのは、
なぜテーブルを分けるのか
を説明できることです。
1対多
実際のデータベース設計で非常によく登場するのが、1対多です。
1対多とは、
ある1件のデータに対して、別のテーブルのデータが複数件対応する関係
です。
ECサイトで考えてみましょう。
1人のユーザーは、1回だけではなく何度も注文できます。
そのため、
ユーザー
│
│ 1
│
│ N
注文
という関係になります。
ここで、
1
は1件、
N
は複数件を表しています。
つまり、
1人のユーザー
│
├─ 注文1
├─ 注文2
└─ 注文3
という関係です。
テーブルでは、
users
-------------------
id
name
email
created_at
orders
-------------------
id
user_id
ordered_at
total_amount
と表現できます。
例えば、山田太郎さんのIDが「1」で、
| id | name |
|---|---|
| 1 | 山田太郎 |
だったとします。
山田さんが3回注文すれば、
| id | user_id | ordered_at |
|---|---|---|
| 101 | 1 | 2026-09-01 |
| 102 | 1 | 2026-09-05 |
| 103 | 1 | 2026-09-10 |
のようになります。
すべての注文が、
user_id = 1
によって山田さんを参照しています。
これが1対多の基本的な形です。
親テーブルと子テーブル
このような関係を説明するとき、
- 親テーブル
- 子テーブル
という言葉が使われることがあります。
先ほどの例では、
users
│
│ 1
│
│ N
orders
となっています。
orders.user_idからusers.idを参照しているため、この関係について考えると、
users → 親テーブル
orders → 子テーブル
と呼ぶことができます。
イメージとしては、
親:users
ユーザー1
│
├─ 注文101
├─ 注文102
└─ 注文103
子:orders
という形です。
親・子はテーブルに固定された役割ではない
ただし、
usersは常に親テーブル
ordersは常に子テーブル
というわけではありません。
例えば、
users
│
orders
│
order_items
という関係があった場合、
ordersはusersとの関係では子ですが、
order_itemsとの関係では親になります。
つまり、
親テーブル・子テーブルという呼び方は、どの参照関係について話しているかによって変わる
ということです。
外部キーは基本的に「多」の側に置く
1対多の関係を設計するとき、
「どちらのテーブルに相手のIDを持たせればよいのか?」
と迷うことがあります。
基本的には、
「多」の側から「1」の側を参照する
と考えると分かりやすいです。
例えば、
ユーザー 1
│
│
N
注文
であれば、「多」の側であるordersに、
user_id
を持たせます。
users
-------------------
id
orders
-------------------
id
user_id ← users.idを参照
注文側から、
「この注文は、どのユーザーのものなのか」
を表しているわけです。
多対多
次に、多対多です。
多対多とは、
一方の複数のデータと、もう一方の複数のデータが互いに関連する関係
です。
ECサイトの、
注文
商品
を考えてみましょう。
1つの注文には、複数の商品を含めることができます。
例えば、
注文101
├─ パソコン
├─ マウス
└─ キーボード
という注文ができます。
一方、同じ商品が別々の注文に含まれることもあります。
マウス
├─ 注文101
├─ 注文105
└─ 注文110
つまり、
1つの注文 → 複数の商品
1つの商品 → 複数の注文
という関係です。
概念的には、
注文 N
│
│
│
│ N
商品
となり、これが多対多です。
多対多では中間テーブルを使う
多対多の関係をリレーショナルデータベースで表現するときは、
中間テーブル
を使うのが一般的です。
今回の例では、
order_items
という「注文商品」を管理するテーブルを間に作ります。
注文
│
│ 1
│
│ N
注文商品
│
│ N
│
│ 1
商品
英語のテーブル名で表すと、
orders
│
│ 1
│
│ N
order_items
│
│ N
│
│ 1
products
となります。
例えば、
orders
-------------------
id
user_id
ordered_at
order_items
-------------------
id
order_id
product_id
quantity
products
-------------------
id
name
price
というテーブルを作ります。
ここでorder_itemsには、
order_id
product_id
という2つの参照を持たせています。
中間テーブルのデータを見てみる
例えば、商品が次のように登録されているとします。
| id | name |
|---|---|
| 201 | パソコン |
| 202 | マウス |
| 203 | キーボード |
そして、注文ID「101」で、
- パソコンを1個
- マウスを2個
購入したとします。
order_itemsは、例えば次のようになります。
| id | order_id | product_id | quantity |
|---|---|---|---|
| 1 | 101 | 201 | 1 |
| 2 | 101 | 202 | 2 |
1行目は、
order_id = 101
product_id = 201
なので、
注文101に商品201(パソコン)が含まれている
ことを表します。
2行目は、
order_id = 101
product_id = 202
なので、
注文101に商品202(マウス)が含まれている
ことを表しています。
つまり、中間テーブルは、
「どのデータと、どのデータが関連しているのか」
を管理する役割を持っています。
多対多は「2つの1対多」と考える
多対多は、最初は少し理解しにくいかもしれません。
その場合は、
2つの1対多に分解する
と考えると分かりやすくなります。
まず、
注文
│ 1
│
│ N
注文商品
です。
1つの注文には、複数の注文商品が存在します。
次に、
商品
│ 1
│
│ N
注文商品
です。
1つの商品は、複数の注文商品から参照されます。
つまり、
注文 N ─ N 商品
という多対多の関係を、
注文 1 ─ N 注文商品
商品 1 ─ N 注文商品
という2つの1対多に分解しているわけです。
多対多の設計で迷ったときは、
間に中間テーブルを置き、2つの1対多として考える
と理解すると分かりやすいでしょう。
中間テーブルには「関係に関する情報」も持たせられる
中間テーブルには、2つのIDだけではなく、
その関係そのものについての情報
を持たせることもできます。
例えば、
order_items
-------------------
id
order_id
product_id
quantity
unit_price
とします。
quantityは、
「この注文では、この商品を何個購入したのか」
という情報です。
例えば、
注文101 × マウス → 2個
注文102 × マウス → 1個
のように、同じ商品でも注文によって数量が変わります。
この数量は、
orders
だけの情報でも、
products
だけの情報でもありません。
「注文と商品の組み合わせ」
に対する情報です。
そのため、中間テーブルであるorder_itemsに持たせるのが自然です。
unit_priceについても、例えば購入時点の商品単価を保存するのであれば、
「その注文で、その商品をいくらで購入したのか」
という注文商品に関する情報として持たせることができます。
この、
その情報は、何についての情報なのか
という考え方は、テーブル設計では非常に重要です。
1対1・1対多・多対多を整理する
ここまでの内容を一度整理してみましょう。
1対1
ユーザー
│
│ 1
│
│ 1
プロフィール
1人のユーザーに対して、プロフィールが最大1件対応する関係です。
1対多
ユーザー
│
│ 1
│
│ N
注文
1人のユーザーに対して、複数の注文が存在する関係です。
実際のデータベース設計では、非常によく登場します。
多対多
概念的には、
注文 N ─ N 商品
です。
実際には中間テーブルを使い、
注文
│ 1
│
│ N
注文商品
│ N
│
│ 1
商品
のように表現します。
「0件でもよいのか」も考える
ここまで、
1対1
1対多
多対多
という分類について説明しました。
しかし、実際の設計ではもう1つ考えておきたいことがあります。
それが、
0件でもよいのか
という点です。
例えば、新しく会員登録したユーザーは、まだ注文をしたことがないかもしれません。
そのため、ユーザーと注文の関係は、より正確には、
1人のユーザーには0件以上の注文が存在する
と考えることができます。
ユーザー
│
│
└─ 注文 0件以上
プロフィールについても、
プロフィール登録は任意
というシステムなら、
1人のユーザーに対してプロフィールは0件または1件
となります。
ER図などでは、
0..1
0..N
1..N
のような表記で最小件数と最大件数を表すことがあります。
最初のうちは、
1対1
1対多
多対多
という基本を理解したうえで、
「0件という状態は許可されるのか?」
もあわせて確認するとよいでしょう。
関係を判断するときは日本語で考える
1対1・1対多・多対多を判断するとき、いきなりテーブル構造を考えると分かりにくい場合があります。
そのようなときは、まず、
業務上の関係を日本語の文章にする
と考えやすくなります。
例えば、ユーザーと注文なら、
1人のユーザーは、複数の注文を行うことができる。
1つの注文は、1人のユーザーに属する。
となります。
つまり、
ユーザー 1 ─ N 注文
です。
注文と商品なら、
1つの注文には、複数の商品を含めることができる。
1つの商品は、複数の注文に含まれることができる。
となります。
両方とも「複数」なので、
注文 N ─ N 商品
となり、多対多であることが分かります。
そこで中間テーブルを作り、
注文
│
注文商品
│
商品
という形にします。
迷ったときは、
「AはBをいくつ持てるのか?」
「BはAをいくつ持てるのか?」
という2方向から確認すると、関係を整理しやすくなります。
関係はシステムの要件によって変わる
テーブル同士の関係には、
「このテーブル同士なら必ずこの関係になる」
という決まりがあるわけではありません。
例えば、
ユーザー
住所
について考えてみます。
1ユーザーにつき住所を1件しか登録できないシステムなら、
ユーザー 1 ─ 1 住所
になるかもしれません。
一方、
- 自宅
- 勤務先
- 実家
- その他の配送先
など、複数の住所を登録できるシステムなら、
ユーザー 1 ─ N 住所
になります。
つまり、同じ「ユーザー」と「住所」であっても、システムの要件によって関係は変わります。
そのためリレーション設計でも、第2回で行った要件整理が重要になります。
実際のシステムでは、データ同士がどのような関係になっているのか
を確認して設計しましょう。
ECサイト全体の関係を見てみる
最後に、今回使用したECサイトのテーブルをまとめてみます。
ユーザー
│
│ 1
│
│ N
注文
│
│ 1
│
│ N
注文商品
│
│ N
│
│ 1
商品
テーブル名にすると、
users
│
│ 1
│
│ N
orders
│
│ 1
│
│ N
order_items
│
│ N
│
│ 1
products
そして、主な参照関係は、
users.id
↑
│
orders.user_id
orders.id
↑
│
order_items.order_id
products.id
↑
│
order_items.product_id
となります。
複数のテーブルをこのように関連付けることで、
- あるユーザーが行った注文
- ある注文に含まれている商品
- ある商品が含まれている注文
- あるユーザーが過去に購入した商品
などをたどれるようになります。
テーブルを1つずつ独立して考えるだけでなく、
システム全体でテーブルがどのようにつながっているのか
を見ることが、リレーション設計では重要です。
まとめ
今回は、テーブル同士の関係を設計する方法について解説しました。
テーブル同士の基本的な関係には、
1対1
1対多
多対多
があります。
1対1は、
ユーザー 1 ─ 1 プロフィール
のように、1件に対して最大1件が対応する関係です。
1対多は、
ユーザー 1 ─ N 注文
のように、1件に対して複数件が対応する関係です。
1対多では基本的に、「多」の側から「1」の側を外部キーで参照します。
多対多は、
注文 N ─ N 商品
のように、双方が複数のデータと関連する関係です。
多対多の関係は、通常は中間テーブルを使って、
注文 1 ─ N 注文商品
商品 1 ─ N 注文商品
という2つの1対多として表現します。
また、テーブル同士を関連付けるときには外部キーを設定することで、
存在しないユーザーを参照する注文
のような不整合を防ぐことができます。
リレーションを考えるときは、
AはBをいくつ持てるのか?
BはAをいくつ持てるのか?
という2方向から考えてみると、関係を整理しやすくなります。
そして何より重要なのは、
実際のシステムや業務では、データ同士がどのような関係になっているのか
を確認することです。
第3回では1つ1つのテーブルやカラムを設計しました。
今回の第4回では、それらのテーブルをつなぎ、システム全体としてどのようなデータ構造にするのかを考えました。
テーブルを設計したあとは、それぞれを独立したものとして見るのではなく、テーブル同士のつながりもあわせて整理していきましょう。