付録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_pinned…is_pinned(固定か)という列を足す。boolean… 中身は true / false の2択。not null default false… 空を作らない。初期値はfalse(固定していない)。第6章のis_publicと同じ作法です。
🔧 フラグ設計の注意
-
3つ以上の状態は、フラグを増やすより
status列1本に。 「下書き / 公開 / アーカイブ」のように状態が3つ以上あるなら、is_draftis_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_ais_bis_c…と増えると読みにくくなります。まとまる状態はstatusに集約を。
🆔 ID と インデックス — 似て非なる2つ(🟢 基礎)
名前が似ていて、初心者がいちばん混同しやすい2つです。まず一言で区別します。
- ID(識別子) … その行が「誰か」を表す 名札・背番号。例:
tweets.id、auth.users.id。重複しない。- インデックス(索引) … 目的の行を 速く見つけるための 仕組み。本の巻末の索引と同じ。
📚 たとえ話:図書館
- ID = 本につけた請求番号。その1冊を一意に指す「名札」。
- インデックス =「著者名 → 棚の場所」の索引カード。目的の本に速くたどり着くための「探す道具」。 番号(ID)と索引(インデックス)は、役割がまったく別物ですよね。データベースでも同じです。
🔧 なぜ混同するの? → 主キーには索引が自動で付くから
id を primary 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つ作らずstatus列(check制約つき)で設計して」- 「削除は物理削除でなく ソフトデリート(
deleted_at) にして、表示クエリと RLS で消した行を除いて」
チェックポイント:
where/order by/joinに使う列に 索引があるか?- 不要な索引を量産していないか?
- 状態管理が boolean 乱立になっていないか(
statusでまとまらないか)?
📝 ことばメモ
- フラグ:
true/falseで状態を表す列(is_publicなど) - ソフトデリート:本当に消さず「消した印(
is_deleted/deleted_at)」を付ける方式 status列:3つ以上の状態を1列で持つ(draft/published/… のように)- ID(識別子):行を一意に指す名札(主キー)
- インデックス(索引):目的の行を速く見つける仕組み
- 全表スキャン:索引がなく、全行を順に見ること(遅い)
- 複合インデックス:複数の列をまとめた索引(
(user_id, created_at)など)
↩️ もどる
- フラグの実例は 第6章 公開タイムライン(
is_public)、IDの話は 第2章 ユーザーとID(UUID)、索引が効く取り出しは 第8章・第12章 を合わせてどうぞ。