DB DESIGN

〇〇したい」から
テーブルが浮かぶ

要件を聞いたとき、
どんなテーブルを作ればいいかが分かるようになる

要件を聞いたとき、
テーブル設計が浮かびますか?

「ユーザーが複数のチームに入れるようにしたい」
「削除しても後から見返せるようにしたい」
「注文した時点の価格を残したい」 …こういう要件を聞いたとき、どんなテーブルを作ればいいか、すぐにイメージできますか?
今日は よくある要件を1つずつ取り上げて
それぞれどんなテーブル構造にすればいいかを見ていきます。
QUESTION 01

「記事を投稿できるようにしたい」

田中さん 初めての投稿 2つ目の投稿 3つ目の投稿 1人 → 複数の記事 これが「1対多」の関係
posts テーブル
iduser_idtitle
11初めての投稿
212つ目の投稿
32佐藤の投稿
posts に user_id を持たせて「この記事は誰のものか」を示す。 「〇〇を複数持てる」と聞いたら → 子テーブルに親の id を持たせる。
QUESTION 02

複数のチームに入れるようにしたい」

田中さん 開発チーム デザインチーム X team_id を1つしか持てない → 1チームにしか入れない!
users テーブル(NG)
idnameteam_id
1田中1 ← 1つだけ
2佐藤2
1対多の考え方では1つにしか入れない「互いに複数持てる」関係 = 多対多。別の方法が必要です。

多対多は中間テーブルで解決する

田中 佐藤 中間テーブル 田中 × 開発 田中 × デザイン 佐藤 × 開発 開発 デザイン 田中は両方に所属できる!
user_teams(中間テーブル)
user_idteam_id
1 (田中)1 (開発)
1 (田中)2 (デザイン)
2 (佐藤)1 (開発)
「互いに複数持てる」と聞いたら → 中間テーブル。 行を増やすだけで関係を増やせる。
QUESTION 03

「チーム内で役割を分けたい

開発チーム 田中 admin 佐藤 member
user_teams(属性つき中間テーブル)
user_idteam_idrolejoined_at
11admin2025-01-10
21member2025-02-15
中間テーブルは関係そのものの情報を持てる場所。 role, status, position, joined_at … 「その関係の中でどういう立場か」を表す。
QUESTION 04

「下書きと公開を分けたい

draft 書きかけ… published 公開中! status カラムで状態を切り替える
posts テーブル
idtitlestatus
1初めての投稿published
2書きかけの記事draft
3一時停止中suspended
「管理者と一般ユーザーがいる」→ role カラムも同じ考え方。 状態や種類を文字列で持たせるだけのシンプルなパターンです。
QUESTION 05

既読かどうか分かるようにしたい」

! 新着メッセージ 未読 承認されました 既読 NULL = まだ読んでいない 日時あり = 読んだ
notifications テーブル
idmessageread_at
1新着メッセージNULL
2承認されました2025-05-30 10:00
〇〇_at カラムで「やったかどうか + いつやったか」を表す。 confirmed_at, approved_at, deleted_at … 全部同じパターン。
QUESTION 06

「退会しても投稿は残したい

田中(有効) 佐藤(退会済み) 佐藤の投稿① 佐藤の投稿② ← データは残っている
users テーブル
idnamedeleted_at
1田中NULL(有効)
2佐藤2025-05-01
本当にデータを消す(物理削除)のではなく、deleted_at に日時を入れるこれを「論理削除」と呼びます。データは残るので、佐藤さんの投稿も消えません。
QUESTION 07

「記事にタグをつけたい」

NG: カンマ区切りで保存
"Ruby,Rails,SQL"
idtags
1"Ruby,Rails,SQL"

「Ruby のタグがついた記事」を探すのが困難

OK: 別テーブルに分ける
Ruby Rails SQL
post_idtag_id
11 (Ruby)
12 (Rails)

タグごとの検索も集計もできる

QUESTION 08

「電話番号を複数登録したい」

NG: 連番カラム
phone1 phone2 phone3?
idphone1phone2phone3
1090-...080-...NULL

4つ目が必要になったら
テーブル構造を変える必要がある

OK: 別テーブルに分ける
phones テーブル 行を増やすだけで何個でも追加
iduser_idnumber
11090-...
21080-...

何個でも追加できる

QUESTION 09

「注文一覧に顧客名を表示したい」

NG: 顧客名を毎回保存
改姓したら? 田中太郎 田中太郎
idcustomer_nameitem
1田中太郎りんご
2田中太郎みかん

全行を更新する必要がある

OK: customer_id で参照する
1箇所直すだけでOK customers.name
idcustomer_iditem
11りんご
21みかん

名前は customers テーブルの
1箇所だけ直せばいい

QUESTION 10

注文した時点の価格を残したい」

りんご ¥120 値上げ! りんご ¥150 注文記録: りんご ¥120 注文時の価格は変わらない あえてコピーして保存する = スナップショット
orders テーブル
idproduct_idproduct_nameprice
15りんご120
25りんご120
さっきのQ9とは逆で、あえてコピーして保存する。 「変えたくないデータ」はスナップショットとして持つ。正規化の例外です。
QUESTION 11

「記事にも商品にもコメントしたい」

コメント 記事 (Post) 商品 (Product) type=Post type=Product type + id で「何に対するコメントか」を表す 通知やお気に入りでも同じパターン
comments テーブル
idcommentable_typecommentable_idbody
1Post3いい記事
2Product7気に入った
これをポリモーフィックと呼びます。 コメント先が1種類なら普通の外部キーで OK。複数種類あるときにこのパターンを使います。
QUESTION 12

変更履歴を残したい」

タイトルを「旧」→「新」に変更 ステータスを draft → published 本文を更新 いつ・誰が・何を変えたかを記録
post_histories テーブル
post_idchanged_byfieldoldnew
31title旧タイトル新タイトル
31statusdraftpublished
専用の履歴テーブルを作って記録する。 元のテーブル名 + _histories が定番の命名です。
CHECKLIST

設計の危険信号

こんな兆候が見えたら、テーブル設計を見直すサインです。
!
「同じ情報を複数箇所に書いている」→ テーブル分割して参照
!
「〇〇を変えたら全レコード更新」→ 正規化不足
!
「カラム名に連番がある (〇〇_1, 〇〇_2)」→ 別テーブルへ
!
「NULL だらけのカラムがある」→ テーブル分割を検討
!
「カラムが 20 個を超えてきた」→ テーブル分割を検討
5 QUESTIONS

設計前に確認する 5 問

1
この値が変わったとき、何行の更新が必要? 複数行なら正規化すべき
2
このカラム、NULL が多くならない? 多いなら別テーブルへ
3
このデータ、他のテーブルにも同じものがない? あるなら外部キーで参照
4
検索や集計に使う? 使うなら正規化してカラムに持つ
5
将来この項目が増える可能性がある? あるなら別テーブルに

今日のまとめ

1
「複数持てる」→ 1対多(子テーブルに親の id を持たせる)
2
「互いに複数持てる」→ 多対多(中間テーブル)
3
「状態を分けたい」→ status、「済みかどうか」→ 〇〇_at
4
「消しても残したい」→ deleted_at(論理削除)
5
カンマ区切り・連番カラム・データ重複 → 全部 NG。別テーブルに
6
「その時点の値を残したい」→ スナップショット(あえてコピー保存)
DB DESIGN

要件を聞いたら
テーブルが浮かぶ

「〇〇したい」の裏にあるのは、
いつも同じ設計パターンの組み合わせです。 — ご清聴ありがとうございました
1 / 19