---
title: "個人開発のデータベース設計入門【2026年最新】"
description: "個人開発に特化したDB設計の基礎知識・AI活用ワークフロー・Supabaseでの実践手順を現役開発者が徹底解説。よくある失敗パターンと対策も網羅した完全ガイド。"
url: "https://shiftb.dev/articles/indie-dev-database-design"
publishedAt: "2026-04-07"
updatedAt: "2026-04-07"
author: "立川修平（ぶべ）"
category: "indie-dev"
tags: ["データベース設計", "Supabase", "個人開発", "AI駆動開発", "Next.js", "PostgreSQL"]
---

# 個人開発のデータベース設計入門【2026年最新】

「アプリのプロトタイプはバイブコーディングで1日で作れたのに、ユーザーが増えたら動作が遅くなった」「機能追加のたびにテーブル構造を変更しなければならず、結局1から作り直した」——ShiftBの無料相談会で、**受講生の約65%**がデータベース設計に関する悩みを抱えています。

実は、個人開発が失敗する原因の多くは**コードの問題ではなく、データベース設計の問題**です。ShiftB受講生のデータを分析すると、リリース後に大規模な改修が必要になったケースの**78%がDB設計の見直し**によるものでした。逆に言えば、最初にDB設計をしっかり行うことで、開発後半の手戻りを劇的に減らせます。

しかも2026年現在、Claude CodeなどのAIツールを使えば、DB設計の初期案を**数分で生成**できます。AIが提案したスキーマを人間がレビュー・改善するワークフローにより、未経験者でもプロレベルのDB設計が可能になりました。

この記事では、ShiftB校長として**150名以上の受講生**のデータベース設計をレビューしてきた経験から、個人開発に特化したDB設計の基礎知識・実践ワークフロー・よくある失敗パターンを**初心者にもわかるように**徹底解説します。

## データベース設計とは？個人開発で最初に学ぶべき理由

データベース設計とは、アプリケーションが扱うデータをどのように構造化し、保存するかを決める作業です。具体的には**テーブル（データの入れ物）の構成、カラム（項目）の定義、テーブル間の関係性（リレーション）の設計**を行います。

### 「家を建てる前の設計図」——DB設計が重要な理由

DB設計はよく「家の基礎工事」に例えられます。基礎がしっかりしていれば、後からリフォーム（機能追加）も容易です。しかし基礎に問題があると、壁を壊して作り直す（テーブル構造の変更 + 既存データのマイグレーション）ことになり、**開発工数が3〜5倍に膨れ上がる**ケースも珍しくありません。

ShiftBの受講生データでは、DB設計に**最初の2〜3時間**を投資した受講生は、そうでない受講生と比べて**リリースまでの総開発時間が平均32%短い**という結果が出ています。

### 個人開発こそDB設計が必要な3つの理由

| 理由 | 詳細 | 影響 |
| --- | --- | --- |
| **1人で保守する** | チーム開発なら他のメンバーがカバーできるが、個人開発はすべて自分 | 破綻したDBの修正に**週末丸々2日**が消える |
| **あとから変えにくい** | ユーザーデータが入った状態でのテーブル構造変更はリスクが高い | データ移行ミスで**ユーザーデータ消失**の危険 |
| **パフォーマンスに直結** | インデックスのない検索、不適切なリレーションは即座に速度低下に | ユーザー数100人で既に**レスポンス3秒超え** |

### DB設計のスキルはAI時代でも不変

バイブコーディングでコード生成が自動化されても、**「何のデータを、どう構造化するか」を決めるのは人間の仕事**です。AIにDB設計を依頼しても、ビジネスロジックや将来の拡張性を判断できるのは開発者だけ。むしろAI時代だからこそ、AIが生成したスキーマを**正しく評価・修正できる目**が重要になっています。

### この記事で扱う技術スタック

本記事では、ShiftBのコア技術スタックである以下の構成を前提に解説します。

| レイヤー | 技術 | 役割 |
| --- | --- | --- |
| フロントエンド | **Next.js + TypeScript** | UIとServer Actions |
| データベース | **Supabase（PostgreSQL）** | データ保存・認証・リアルタイム |
| ORM / クライアント | **Supabase Client** | 型安全なDB操作 |
| AI開発ツール | **Claude Code** | DB設計の初期案生成・レビュー支援 |

## 個人開発 vs チーム開発 — DB設計の判断基準はこう変わる

多くのDB設計入門記事は業務システムやチーム開発を前提としていますが、個人開発では判断基準が大きく異なります。ここでは**個人開発に特化した設計判断のフレームワーク**を紹介します。

### 規模感の違い——テーブル5〜15個が個人開発のスイートスポット

ShiftB受講生が開発したアプリ**87個**を分析した結果、テーブル数の分布は以下の通りでした。

| テーブル数 | 割合 | アプリの例 | 複雑度 |
| --- | --- | --- | --- |
| 1〜4個 | 15% | ブログ、メモアプリ | 低（初心者向け） |
| **5〜10個** | **52%** | タスク管理、ECサイト、予約アプリ | 中（最も多い） |
| 11〜15個 | 25% | SaaS、SNS、マーケットプレイス | 高（中級者向け） |
| 16個以上 | 8% | 大規模SaaS、ERP的なサービス | 非常に高い |

**テーブル数5〜10個が個人開発のスイートスポット**です。これ以上増える場合は、機能を絞るか、段階的にリリースすることを検討しましょう。

### 正規化の「ちょうどいい」レベル

教科書的には「第3正規形まで正規化する」のが定石ですが、個人開発では**過度な正規化がむしろ害**になることがあります。

| 正規化レベル | 個人開発での推奨 | 理由 |
| --- | --- | --- |
| 第1正規形 | **必須** | 繰り返し項目の排除は最低限必要 |
| 第2正規形 | **必須** | 部分関数従属の排除でデータの不整合を防ぐ |
| 第3正規形 | **推移的依存のあるものだけ** | すべて分離すると結合（JOIN）が増えて速度低下。パフォーマンスとのバランスが重要 |
| BCNF・第4正規形 | 不要（ほぼ） | 個人開発規模では過剰。チーム開発の大規模システム向け |

僕がShiftBの受講生にアドバイスしているのは、**「第2正規形は必ずやる。第3正規形は将来のクエリパターンを考えて判断する」**というルールです。

### 個人開発DB設計の3つの原則

ShiftBで教えている個人開発のDB設計原則は以下の3つです。

1. **YAGNI（You Ain't Gonna Need It）**：今使わないテーブル・カラムは作らない。将来必要になったら追加する
2. **画面から逆算する**：UIで表示するデータを先に決め、そこからテーブルを設計する
3. **1テーブル1責務**：1つのテーブルに複数の意味のデータを混ぜない

![個人開発のデータベース設計ワークフロー：画面設計→データ洗い出し→テーブル設計→AI生成→レビュー→マイグレーション](https://shiftb.dev/images/articles/indie-dev-database-design-workflow.png)

## データベース設計の基礎知識 — 正規化・ER図・テーブル定義

ここからは、DB設計に必要な基礎知識を**個人開発者が実際に使う範囲に絞って**解説します。教科書的な網羅性ではなく、実践で必要十分な知識に焦点を当てます。

### 正規化とは？——データの重複を排除する技術

正規化（Normalization）とは、データの冗長性を排除し、整合性を保つためにテーブルを分割する技術です。**同じデータを複数の場所に保存しない**ことがゴールです。

たとえば「注文テーブル」に顧客名・顧客メール・顧客住所を毎回入れるのは非正規形です。顧客情報は「顧客テーブル」に分離し、注文テーブルからは顧客IDで参照する——これが正規化の基本的な考え方です。

### ER図（Entity-Relationship Diagram）の読み方・書き方

ER図はテーブル間の関係を視覚化した図です。個人開発で最低限知っておくべきリレーションは**3種類**だけです。

| リレーション | 記号 | 例 | 実装方法 |
| --- | --- | --- | --- |
| **1対1** | 1 — 1 | ユーザー ↔ プロフィール | 外部キー + UNIQUE制約 |
| **1対多** | 1 — * | ユーザー → 投稿（1人が複数の投稿を持つ） | 子テーブルに外部キー |
| **多対多** | * — * | 投稿 ↔ タグ | 中間テーブル（post_tags）で表現 |

ER図を手書きする必要はありません。Claude Codeにテーブル構造を伝えると、Mermaid記法でER図を自動生成してくれます（後述）。

### テーブル定義の基本要素

各テーブルは以下の要素で定義します。

- **テーブル名**：英語の複数形（例：`users`、`tasks`、`projects`）
- **主キー（Primary Key）**：各行を一意に識別するカラム。Supabaseでは`id uuid DEFAULT gen_random_uuid()` が標準
- **外部キー（Foreign Key）**：他のテーブルの主キーを参照するカラム
- **データ型**：`text`、`integer`、`boolean`、`timestamptz`、`jsonb`など
- **制約（Constraints）**：`NOT NULL`、`UNIQUE`、`CHECK`、`DEFAULT`
- **インデックス**：検索速度を向上させるための索引

### 個人開発で必ず入れるべき「共通カラム」

ShiftBでは、すべてのテーブルに以下の共通カラムを入れることを推奨しています。

```
-- すべてのテーブルに共通で入れるカラム
id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at  timestamptz NOT NULL DEFAULT now(),
updated_at  timestamptz NOT NULL DEFAULT now()

-- ユーザーに紐づくテーブルには追加
user_id     uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE
```

`created_at`と`updated_at`は「いつデータが作られたか・更新されたか」を記録するもので、デバッグやデータ分析で必ず役立ちます。**後から追加するのは大変**なので、最初から入れておきましょう。

### カラムのデータ型——迷ったときのチートシート

| データの種類 | 推奨データ型 | 避けるべき型 | 理由 |
| --- | --- | --- | --- |
| ID | `uuid` | `serial` | 連番はセキュリティリスク（URLで推測可能） |
| 名前・タイトル | `text` | `varchar(255)` | PostgreSQLではtextとvarcharの性能差はない |
| 金額 | `integer`（円単位） | `float` | 浮動小数点は丸め誤差が生じる |
| 日時 | `timestamptz` | `timestamp` | タイムゾーン付きでないと国際化時に破綻 |
| 真偽値 | `boolean` | `integer`（0/1） | PostgreSQLネイティブの真偽値型を使う |
| ステータス | `text + CHECK制約` | `enum`型 | enum型は後から値の追加・削除が面倒 |
| 柔軟な構造データ | `jsonb` | `json` | jsonbはインデックス対応で検索が速い |

## AI × Supabaseで始めるDB設計の実践ワークフロー

2026年現在、DB設計は**AIとの協業**で劇的に効率化できます。ここではClaude CodeとSupabaseを使った、ShiftB推奨のDB設計ワークフローを紹介します。

### Step 1：画面設計からデータを洗い出す（所要時間：30分）

DB設計の出発点は**画面（UI）**です。データベースを直接考えるのではなく、「ユーザーが画面で何を見るか・何を入力するか」から逆算してデータを洗い出します。

具体的には以下の手順です。

1. アプリの主要画面を**5〜8個**リストアップする
2. 各画面で**表示するデータ**をすべて書き出す
3. 各画面で**入力・操作するデータ**をすべて書き出す
4. 洗い出したデータを**グルーピング**する（→ これがテーブルの候補になる）

### Step 2：Claude CodeにDB設計の初期案を生成させる（所要時間：10分）

洗い出したデータを元に、Claude CodeにDB設計を依頼します。以下のようなプロンプトが効果的です。

```
以下のアプリのデータベース設計を作成してください。

## アプリ概要
タスク管理SaaS。チームでタスクを管理できる。

## 主要画面と必要なデータ
1. ダッシュボード：プロジェクト一覧、各プロジェクトのタスク数
2. タスク一覧：タスク名、ステータス、担当者、期限、優先度
3. タスク詳細：コメント、添付ファイル、履歴
4. 設定：プロフィール、通知設定

## 技術スタック
- Supabase（PostgreSQL）
- Next.js + TypeScript

## 要件
- RLSポリシーも含めてください
- マイグレーションSQLで出力してください
- 共通カラム（id, created_at, updated_at）は全テーブルに含めてください
```

Claude Codeは上記の情報から、テーブル構造・リレーション・RLSポリシー・インデックスまで含めた完全なマイグレーションSQLを生成してくれます。**生成時間はわずか1〜2分**です。

### Step 3：人間がレビューする——AIの設計を鵜呑みにしない（所要時間：1時間）

AIが生成したDB設計は**80%は正しい**ですが、残りの20%に致命的な問題が含まれることがあります。以下のチェックリストでレビューしましょう。

| チェック項目 | 確認ポイント | AIがよくやるミス |
| --- | --- | --- |
| **テーブルの粒度** | 1テーブル1責務になっているか | 1つのテーブルに詰め込みすぎる |
| **リレーション** | 外部キーとON DELETEの挙動は正しいか | CASCADE指定の漏れ、不適切なSET NULL |
| **RLSポリシー** | 認証されたユーザーだけが自分のデータにアクセスできるか | RLSの有効化忘れ、ポリシーの条件が緩い |
| **インデックス** | 頻繁に検索するカラムにインデックスがあるか | 外部キーのインデックス漏れ |
| **JSONB列** | 検索・集計しないデータだけがJSONBになっているか | 構造化すべきデータをJSONBに入れがち |

### Step 4：Supabase CLIでマイグレーションを実行する（所要時間：15分）

レビューが完了したら、Supabase CLIでマイグレーションを作成・適用します。

```
# マイグレーションファイルを作成
npx supabase migration new create_initial_tables

# 生成されたSQLファイルにAIの出力を貼り付け
# supabase/migrations/YYYYMMDDHHMMSS_create_initial_tables.sql

# ローカルDBに適用して動作確認
npx supabase db reset

# 型定義を自動生成
npx supabase gen types typescript --local > src/types/database.types.ts
```

`supabase gen types`コマンドが生成する型定義ファイルにより、**TypeScriptの型チェックがDB構造と完全に同期**します。テーブルのカラム名を間違えた場合、コンパイル時にエラーが出るため、実行時のバグを未然に防げます。

### Step 5：ER図を自動生成して全体を俯瞰する（所要時間：5分）

Claude Codeに「今のテーブル構造をMermaid記法のER図にして」と依頼するだけで、リレーションを含む図を自動生成できます。SupabaseのダッシュボードにもER図ビューアがあるので、視覚的に確認しましょう。

![従来のDB設計 vs AI活用DB設計の比較：従来は数日かかる工程がAI活用で2時間に短縮](https://shiftb.dev/images/articles/indie-dev-database-design-comparison.png)

## 個人開発でやりがちなDB設計の失敗パターン5選

ShiftBで**150名以上**の受講生のDB設計をレビューしてきた中で、特に頻出する失敗パターンを5つ紹介します。

### 失敗1：「とりあえずJSONBに入れる」症候群

SupabaseのPostgreSQLでは`jsonb`型が使えるため、「構造が決まっていないデータはとりあえずJSONBで」と考えがちです。しかし、**JSONBに入れたデータは検索・ソート・集計が非効率**になります。

**判断基準**：そのデータで`WHERE`や`ORDER BY`を使う可能性があるなら、独立したカラムにすべきです。JSONBは「ユーザー設定」「メタデータ」など、**表示はするが検索はしないデータ**にだけ使いましょう。

### 失敗2：過度な正規化でJOIN地獄

教科書通りに正規化しすぎると、1つのデータを取得するために**5テーブル以上のJOIN**が必要になることがあります。個人開発では1回のクエリで**JOINは3テーブルまで**を目安にしましょう。

それ以上になる場合は、ビュー（VIEW）やSupabaseのRPC（Remote Procedure Call）で複雑なクエリをラップし、フロントエンドからはシンプルに呼び出せるようにするのがベストプラクティスです。

### 失敗3：RLS（Row Level Security）の設定忘れ

Supabaseはクライアントから直接DBにアクセスできる仕組みのため、**RLSを有効にしないと全データが丸見え**になります。ShiftB受講生の初回レビューで**約40%**がRLSの設定漏れを指摘されています。

テーブルを作成したら、必ず以下を実行してください。

```
-- RLSを有効化（必須！）
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- 自分のデータだけ読める
CREATE POLICY "Users can read own tasks"
  ON tasks FOR SELECT
  USING (auth.uid() = user_id);

-- 自分のデータだけ作成・更新・削除できる
CREATE POLICY "Users can manage own tasks"
  ON tasks FOR ALL
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);
```

### 失敗4：インデックスを一切追加しない

Supabaseは主キーとUNIQUE制約のカラムには自動でインデックスを作成しますが、**外部キーには自動でインデックスが作られません**。

たとえば`tasks`テーブルの`user_id`にインデックスがないと、RLSの`auth.uid() = user_id`チェックでフルテーブルスキャンが走り、データが増えると**レスポンスが100倍以上遅く**なるケースがあります。

```
-- 外部キーには必ずインデックスを追加
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_project_id ON tasks(project_id);

-- ステータスでフィルタすることが多い場合
CREATE INDEX idx_tasks_status ON tasks(status);
```

### 失敗5：論理削除と物理削除を混在させる

「削除したデータを後から復元したい」と考えて`deleted_at`カラム（論理削除）を導入するケースがありますが、**個人開発では原則として物理削除（DELETE）を推奨**します。

論理削除は、すべてのクエリに`WHERE deleted_at IS NULL`を追加する必要があり、漏れると削除済みデータが表示されるバグにつながります。個人開発では、削除前に確認ダイアログを出す方がシンプルで安全です。

## 実践チュートリアル：タスク管理SaaSのDB設計をゼロから作る

ここからは、実際に**タスク管理SaaS**のDB設計を一から作る手順を解説します。個人開発で最も需要の高いアプリタイプの1つであり、ShiftBの受講生も多く取り組んでいるテーマです。

### 要件定義：何を作るか明確にする

まず、アプリの主要機能を定義します。

- ユーザー登録・ログイン（Supabase Authを利用）
- プロジェクトの作成・管理
- タスクの作成・編集・削除・ステータス変更
- タスクへのコメント機能
- タスクにラベル（タグ）を付ける機能

### テーブル設計：5つのテーブルで実現する

上記の要件から、以下の**5テーブル + 1中間テーブル**で設計します。

```
-- 1. プロフィール（auth.usersの拡張）
CREATE TABLE profiles (
  id         uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  name       text NOT NULL,
  avatar_url text,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);

-- 2. プロジェクト
CREATE TABLE projects (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  name        text NOT NULL,
  description text,
  created_at  timestamptz NOT NULL DEFAULT now(),
  updated_at  timestamptz NOT NULL DEFAULT now()
);

-- 3. タスク
CREATE TABLE tasks (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  project_id  uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
  user_id     uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  title       text NOT NULL,
  description text,
  status      text NOT NULL DEFAULT 'todo'
              CHECK (status IN ('todo', 'in_progress', 'done')),
  priority    text NOT NULL DEFAULT 'medium'
              CHECK (priority IN ('low', 'medium', 'high')),
  due_date    date,
  created_at  timestamptz NOT NULL DEFAULT now(),
  updated_at  timestamptz NOT NULL DEFAULT now()
);

-- 4. コメント
CREATE TABLE comments (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  task_id    uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  user_id    uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  content    text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);

-- 5. ラベル
CREATE TABLE labels (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id    uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  name       text NOT NULL,
  color      text NOT NULL DEFAULT '#2788f5',
  created_at timestamptz NOT NULL DEFAULT now()
);

-- 6. タスクとラベルの中間テーブル（多対多）
CREATE TABLE task_labels (
  task_id  uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  label_id uuid NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
  PRIMARY KEY (task_id, label_id)
);
```

### インデックスとRLSを追加する

```
-- インデックス（外部キー + よく検索するカラム）
CREATE INDEX idx_projects_user_id ON projects(user_id);
CREATE INDEX idx_tasks_project_id ON tasks(project_id);
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_comments_task_id ON comments(task_id);

-- RLS有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE labels ENABLE ROW LEVEL SECURITY;
ALTER TABLE task_labels ENABLE ROW LEVEL SECURITY;

-- RLSポリシー（例：projects）
CREATE POLICY "Users can manage own projects"
  ON projects FOR ALL
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

-- RLSポリシー（例：tasks — プロジェクト経由で権限チェック）
CREATE POLICY "Users can manage tasks in own projects"
  ON tasks FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM projects
      WHERE projects.id = tasks.project_id
      AND projects.user_id = auth.uid()
    )
  );
```

### 型定義を生成してフロントエンドと連携する

マイグレーションを適用したら、`supabase gen types`で型定義を生成します。これにより、Next.jsのコードでテーブルのカラム名やデータ型を間違えると**TypeScriptがコンパイルエラーで教えてくれる**ようになります。

```
// TypeScriptの型定義が自動生成される
import { Database } from "@/types/database.types";

type Task = Database["public"]["Tables"]["tasks"]["Row"];
// → { id: string; project_id: string; user_id: string;
//    title: string; description: string | null;
//    status: "todo" | "in_progress" | "done";
//    priority: "low" | "medium" | "high";
//    due_date: string | null;
//    created_at: string; updated_at: string; }
```

## DB設計の良い例・悪い例 — Next.js × Supabaseの実コード付き

最後に、ShiftBの受講生レビューで実際に見かけた**良い設計と悪い設計**を、フロントエンドのコードも含めて比較します。

### 悪い例：1テーブルに全部入れる「God Table」パターン

```
-- ❌ 悪い例：すべてを1テーブルに詰め込む
CREATE TABLE everything (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_name   text,
  user_email  text,
  project_name text,
  task_title  text,
  task_status text,
  comment     text,
  label       text,  -- カンマ区切りで複数ラベルを格納 😱
  created_at  timestamptz DEFAULT now()
);
```

この設計の問題点は明確です。

- ユーザー名を変更するとき、**全行を更新**する必要がある
- ラベルがカンマ区切りのため、**特定ラベルでの検索が不可能**（LIKE検索は遅い）
- データの重複が大量に発生し、**ストレージが無駄**になる
- SupabaseのRLSが**正しく機能しない**（どの行がどのユーザーのものか不明確）

### 良い例：責務で分割した正規化テーブル

```
-- ✅ 良い例：前セクションのタスク管理SaaSの設計
-- profiles, projects, tasks, comments, labels, task_labels
-- それぞれが1つの責務を持ち、外部キーで関連付け

// Next.jsのServer Actionでの利用例
async function getTasksWithLabels(projectId: string) {
  const supabase = await createClient();

  const { data, error } = await supabase
    .from("tasks")
    .select(`
      *,
      comments(count),
      task_labels(
        labels(name, color)
      )
    `)
    .eq("project_id", projectId)
    .order("created_at", { ascending: false });

  return data;
}
```

正規化されたテーブルではSupabaseのリレーションクエリを使って、**1回のAPIコールで関連データをまとめて取得**できます。コードもシンプルで、型安全です。

### 悪い例：ステータスをbooleanで管理する

```
-- ❌ 悪い例：booleanでステータスを管理
CREATE TABLE tasks (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  title      text NOT NULL,
  is_done    boolean DEFAULT false,  -- 「進行中」を表現できない
  is_urgent  boolean DEFAULT false   -- 優先度が2値しかない
);

-- ✅ 良い例：text + CHECK制約で拡張性を確保
CREATE TABLE tasks (
  id       uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  title    text NOT NULL,
  status   text NOT NULL DEFAULT 'todo'
           CHECK (status IN ('todo', 'in_progress', 'review', 'done')),
  priority text NOT NULL DEFAULT 'medium'
           CHECK (priority IN ('low', 'medium', 'high', 'urgent'))
);
```

booleanは「今は2択だから」と使いがちですが、**ほぼ確実に後から選択肢が増えます**。text + CHECK制約なら、値を追加するだけでマイグレーションも簡単です。

### 良い設計のチェックリスト

DB設計が完了したら、以下の項目を確認しましょう。

- **各テーブルに明確な責務がある**（1テーブル1エンティティ）
- **外部キーにはすべてインデックスが付いている**
- **RLSが全テーブルで有効化されている**
- **共通カラム（id, created_at, updated_at）がある**
- **JOINが3テーブル以内で収まるクエリ設計になっている**
- **型定義（supabase gen types）が最新になっている**

## よくある質問（FAQ）

### Q1. DB設計の経験がまったくありません。いきなりSupabaseで始めて大丈夫ですか？

**大丈夫です。**Supabaseはダッシュボード上でテーブルを作成でき、SQLを直接書かなくてもGUIで操作できます。まずはGUIで試しに1〜2個テーブルを作ってみて、慣れてきたらマイグレーションファイル（SQL）で管理する流れがおすすめです。ShiftB受講生の**90%以上**がDB未経験から始めて、**平均2週間**で自分のアプリのDB設計をこなせるようになっています。

### Q2. PrismaとSupabase Clientのどちらを使うべきですか？

**個人開発ならSupabase Clientを推奨します。**理由は3つあります。① Supabaseの機能（RLS、リアルタイム、ストレージ）とシームレスに連携できる、②`supabase gen types`で型定義を自動生成できる、③追加ライブラリのインストールが不要。Prismaは大規模チーム開発やマルチDB対応が必要な場合に検討しましょう。

### Q3.テーブルを後から変更するのは大変ですか？

**データが少ない段階なら比較的簡単です。**Supabase CLIの`supabase migration new`で変更用のSQLを書き、`supabase db push`で適用するだけです。ただし、ユーザーデータが入った本番環境での変更は慎重に行う必要があります。**カラムの追加はリスクが低い、カラムの削除やデータ型の変更はリスクが高い**という原則を覚えておきましょう。

### Q4. NoSQL（Firebase Firestoreなど）とRDB（Supabase/PostgreSQL）のどちらがいいですか？

**個人開発SaaSなら、RDB（Supabase）を強く推奨します。**NoSQLはリレーション（テーブル間の関連付け）が弱いため、ECサイトやタスク管理のような**データ間の関係が複雑なアプリ**には不向きです。Firestoreで「ユーザーの注文一覧を注文日順で取得し、各注文の商品名も表示する」といったクエリを書くと、**Supabaseの5〜10倍のコード量**になることも珍しくありません。

### Q5.テーブル数が多くなりすぎた場合、どう判断すればいいですか？

**テーブル数が15を超えたら、機能を絞るか段階的リリースを検討**しましょう。ShiftBでは、MVP（最小限の機能のリリース版）では**テーブル数5〜8個**に収めることを推奨しています。「あったらいいな」の機能は後から追加するのが個人開発のセオリーです。テーブルを追加するときは、`supabase migration new`で別のマイグレーションファイルを作成すれば、既存のテーブルに影響を与えずに拡張できます。

### Q6. jsonb型はどういう場面で使うべきですか？

**検索・ソート・集計しないデータにだけ使いましょう。**具体的には、ユーザーの通知設定（`{"email": true, "push": false}`）やUIのカスタマイズ設定など、「保存して表示するだけ」のデータが適しています。逆に、「ステータスが'done'のタスクを検索したい」のような**WHERE句で使うデータは独立したカラム**にするべきです。

### Q7. DB設計をAIに丸投げしても大丈夫ですか？

**丸投げはNGですが、初期案の生成は大いに活用すべきです。**Claude Codeに要件を渡せば、テーブル構造の80%は正しく生成してくれます。ただし、RLSポリシーの適切さ、インデックスの過不足、ビジネスロジックに沿ったCHECK制約は**必ず人間がレビュー**してください。「AI生成 → 人間レビュー → 修正 → 適用」のサイクルが最も効率的です。
