付録G テーブル設計の小道具 — フラグ・ID・インデックス

📖 このページのゴール:テーブル設計で必ず出会う3つの小道具——フラグIDインデックス——の意味と使いどころが分かる。とくに混同しがちな 「IDとインデックスは別物」 をはっきりさせます。 ← 目次・はじめにへもどる


🚩 フラグ — 「はい / いいえ」を1列で持つ(🟢 基礎)

フラグとは、true / false(はい/いいえ)で状態を表す列のことです。本編の第6章で足した is_public(公開かどうか) が、まさにフラグでした。

書類に貼る「公開」「処理済み」シールのイメージです。1枚貼るかどうかで、状態をパッと表せます。

よく使うフラグの例:

フラグ 意味
is_public 公開か下書きか(第6章)
is_pinned プロフィール上部に固定したツイートか
is_deleted / deleted_at 「消したこと」にする印(後述のソフトデリート)
is_verified 認証済みアカウントか
-- 例:ツイートに「固定」フラグを足す
alter table tweets add column is_pinned boolean not null default false;

1行ずつ読むと:

  • add column is_pinnedis_pinned(固定か)という列を足す。
  • boolean … 中身は true / false の2択
  • not null default false空を作らない。初期値は false(固定していない)。第6章の is_public と同じ作法です。

🔧 フラグ設計の注意

  • 3つ以上の状態は、フラグを増やすより status 列1本に。 「下書き / 公開 / アーカイブ」のように状態が3つ以上あるなら、is_draft is_archived …と boolean を乱立させるより、状態をまとめた1列にした方がスッキリします。

    -- 状態を1列で表す(3つの中からどれか)
    status text not null default 'published'
      check (status in ('draft', 'published', 'archived'));
    
    • check (status in (...))決めた値以外を入れさせない安全装置。
  • 「消す」は本当に消さない手もある(ソフトデリート)。 delete で行ごと消すかわりに、is_deleted = true(または deleted_at に時刻)を立てて 「消したことにする」。あとで復元できる・記録が残る、という利点があります。ただし 表示の select や RLS で「消した行を除く」条件を忘れないこと(忘れると消したはずの投稿が見えます)。

  • フラグの乱立に注意。 is_a is_b is_c …と増えると読みにくくなります。まとまる状態は status に集約を。


🆔 ID と インデックス — 似て非なる2つ(🟢 基礎)

名前が似ていて、初心者がいちばん混同しやすい2つです。まず一言で区別します。

  • ID(識別子) … その行が「誰か」を表す 名札・背番号。例:tweets.idauth.users.id。重複しない。
  • インデックス(索引) … 目的の行を 速く見つけるための 仕組み。本の巻末の索引と同じ。

📚 たとえ話:図書館

  • ID = 本につけた請求番号。その1冊を一意に指す「名札」。
  • インデックス =「著者名 → 棚の場所」の索引カード。目的の本に速くたどり着くための「探す道具」。 番号(ID)と索引(インデックス)は、役割がまったく別物ですよね。データベースでも同じです。

🔧 なぜ混同するの? → 主キーには索引が自動で付くから

idprimary key(主キー)にすると、DBは裏で 「idで探すための索引」を自動で作ります。 だから where id = 123 はいつも速い。この「ID と 索引がセットで付いてくる」のが、両者が同じものに見える原因です。でも——id は名札、索引は探す仕組み。概念は別、と覚えてください。


⚡ インデックスはなぜ要る?(🟢 → 🔧)

索引が 無い と、DBは目的の行を探すのに「全部の行を上から順に見る」しかありません(これを 全表スキャン と呼びます)。10行なら一瞬ですが、100万行になると激重です。

索引が ある と、目的の行へ一直線。

🔎 たとえ話:索引のない辞書は、最初のページから1語ずつめくって探します。あいうえお順の索引があれば一発。これが索引の力です。

どの列に索引を張る?(🟢)

ねらいは、よく「絞り込み(where)」「並べ替え(order by)」「つなぐ(join)」に使う列です。この教材だと、ぴったりの場所がいくつもあります。

  • tweets.user_id … 「この人の投稿」を絞る(第4章・第8章
  • tweets.created_at … 新しい順に並べる(第3章〜、ほぼ全章)
  • follows.follower_id / follows.followee_id … フォロー関係をたどる(第7章・第8章)。第7章の複合主キー (follower_id, followee_id) は、follower_id で探す索引も兼ねます。
-- 「この人の投稿を新しい順」をまとめて速くする索引
create index on tweets (user_id, created_at desc);

1行ずつ読むと:

  • create index on tweets (...)tweets 表に索引を作る。
  • (user_id, created_at desc)user_id で絞ってから created_at の新しい順、という取り出し方に効く 複合インデックス。第8章の「フォロー中の人の投稿を新しい順」がこの形です。

💡 Supabase(PostgreSQL)では、主キーや一部の制約に索引が自動で付きます。でも 自分が検索・並べ替えに使う列は、自動では付かないことが多いので、上のように明示的に張ると速くなります。

🔧 張りすぎの代償

索引は「読みを速く」する代わりに、「書き込み(insert/update)を少し遅く」します。投稿のたびに、本体だけでなく索引も更新しなければならないからです。容量も食います。だから——全部の列に張るのは逆効果。本当に検索・並べ替えに使う列にだけ張りましょう。

🚀 スケールへの橋渡し:第8章の N+1問題や、第12章スケールの物語は、つまるところ「索引が効く形でデータを取れているか」が土台です。索引の設計は、速いアプリへの第一歩です。


⚠️ ハマりどころ

  • ID と インデックスを同じものだと思うID=名札/インデックス=探す仕組み。別物です。
  • 全部の列に索引を張る … 書き込みが遅くなり、容量も無駄。必要な列だけに。
  • 検索・並べ替えに使う列に索引が無い … 全表スキャンで遅い。「遅いな」と思ったら、その where / order by の列に索引があるか確認。
  • ソフトデリートの消し忘れ表示is_deleted の行を除く条件や RLS を入れないと、消したはずが見える。
  • boolean の乱立 … 3つ以上の状態は status 列に集約を検討。

🤖 AIに頼むなら(Vibe codingのコツ)

🗣 プロンプト例:

  • where user_id = ? order by created_at desc が速くなるインデックスを提案して。張りすぎの副作用も教えて」
  • 「状態が draft / published / archived の3つあるので、boolean を3つ作らず statuscheck 制約つき)で設計して」
  • 「削除は物理削除でなく ソフトデリート(deleted_at にして、表示クエリと RLS で消した行を除いて」

チェックポイント:

  1. where / order by / join に使う列に 索引があるか?
  2. 不要な索引を量産していないか?
  3. 状態管理が boolean 乱立になっていないか(status でまとまらないか)?

📝 ことばメモ

  • フラグtrue / false で状態を表す列(is_public など)
  • ソフトデリート:本当に消さず「消した印(is_deleted / deleted_at)」を付ける方式
  • status:3つ以上の状態を1列で持つ(draft/published/… のように)
  • ID(識別子):行を一意に指す名札(主キー)
  • インデックス(索引):目的の行を速く見つける仕組み
  • 全表スキャン:索引がなく、全行を順に見ること(遅い)
  • 複合インデックス:複数の列をまとめた索引((user_id, created_at) など)

↩️ もどる

← 目次・はじめにへもどる