はじめに
データベース設計の品質は、システムの保守性や拡張性に大きな影響を与えます。
特に「命名規則」と「制約の付け方」は、プロジェクト内で統一されていないと、後からデータの意味や関係を理解するのが難しくなります。
本記事では、現場でよく用いられる命名のルールと制約の設計パターンを整理します。
テーブル名のプレフィックス
マスタテーブル (m_)
(例)m_users, m_plans
参照されることが多く、比較的更新頻度が低いテーブル。
コード値や基本情報を保持する。
トランザクションテーブル (t_)
(例)t_orders, t_contracts
業務処理で蓄積されるデータ。履歴性を持つことが多い。
補助的に使うもの
- ログ・監査
log_, audit_ - スナップショット
s_
「性質」を明示することで、初見の人でも「これはどんな種類のテーブルか」をすぐ理解できる!
制約の付け方
主キー(PK:Primary Key)
命名規則例
pk_<テーブル>
(例)pk_m_users
役割
テーブル内で一意に行を識別する。
複合キーも可能だが、実務では単一の id カラムにsurrogate key
を置くのが一般的。
ちょっと深掘り
複合キーも可能だが、実務では単一の id カラムに
surrogate key
を置くのが一般的。
とはどういうことか?
【複合キーも可能】
テーブルの主キー(primary key)は、必ずしも1つのカラムである必要はないということ。
たとえば「注文テーブル」で 注文ID がなくても、 (顧客ID, 商品ID) の組み合わせを"複合キー"として主キーにできるわけです。
(例)
PRIMARY KEY (customer_id, product_id)
【実務では単一の id カラムにsurrogate key
を置くのが一般的】
実務では、複合キーをそのまま主キーにするよりも、
「idカラム(連番やUUIDなど)を主キー専用に作る」ほうが一般的であるということ。
これをsurrogate key
(代理キー)と呼ぶ。
「意味を持つ自然キー(natural key)」とは違い、単純に一意性を保証するためだけの人工キーです。
(例)
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL,
product_id BIGINT NOT NULL
なぜsurrogate key
が好まれるか?
- シンプルにJOINできる
複合キーだとJOIN条件が長くなりがちだが、idなら1カラムで済む。 - 外部キー参照が楽
別テーブルが参照するときもid1つで参照できる。 - 将来の変更に強い
「自然キーの構成」が変わっても、idは変わらない。 - フレームワークやORMで扱いやすい
多くのツールは「idカラム主キー」を前提にしている。
外部キー(FK:Foreign Key)
命名規則例
fk_<参照元テーブル>_<参照先テーブル>
(例)fk_t_orders_m_users
役割
参照整合性を保証。
削除や更新時のON DELETE CASCADE
やON UPDATE CASCADE
の設計方針も明確にする。
ちょっと深掘り
削除や更新時の
ON DELETE CASCADE
やON UPDATE CASCADE
の設計方針も明確にする。
とはどういうことか?
【外部キー制約とCASCADE】
データベースでテーブル同士をリレーションするとき、
「親テーブルの行が削除・更新されたら、子テーブルの行はどうするか?」を決める必要がある。
SQLではそれをON DELETE
やON UPDATE
の句で指定する。
(例)
FOREIGN KEY (customer_id)
REFERENCES customers(id) → customer_id は必ず customers テーブルの id を参照する
ON DELETE CASCADE → 参照先(親)のデータが削除されたら、参照している側(子)も削除する
ON UPDATE CASCADE; → 参照先のキーが更新されたら、参照している側も更新する
【色々オプション】
- ON DELETE CASCADE
親行が削除されたら、子行も一緒に削除される。
(例)顧客が削除されたら、その顧客の注文も削除。 - ON DELETE SET NULL
親行が削除されたら、子行の外部キーを NULL にする。
(例)担当者が退職したら、案件の担当者IDを NULL にする。 - ON DELETE RESTRICT ・ NO ACTION
親行にまだ子行が残っている場合は削除を禁止する。
(例)注文が残っている顧客は削除できない。 - ON UPDATE CASCADE
親のキー値が更新されたら、子のキー値も自動更新する。
(ただし実務では、PKを更新することは少ないので使うケースは稀。)
【色々注意点】
大量削除!?
親1行消したら関連データ数千件が一気に消えた…という事故がある。
削除できない地獄!?
「先に子を全部消さないと親を消せない」ケースが頻発する。
論理削除との相性😭
実務では論理削除(deleted_at など)を採用することが多いので、CASCADEを使用しないことも多い。
(例)
システムで論理削除を採用している。
親を論理削除(deleted_at に日付を入れる)する。
しかし、子テーブルは論理削除されない(CASCADEは発動しない)。
逆にDELETE
を使ったら、親も子も物理的に消えてしまう。
つまり「中途半端」になってしまう。
そのため、論理削除の場合は、「子も論理削除する処理」を明示的に書くようにすると良い。
ユニーク制約(UQ:Unique)
命名規則例
uq_<テーブル>_<カラム>
(例)uq_m_users_email
用途
メールアドレスや社員番号など、一意性が求められる項目。
チェック制約(CK:Check)
命名規則例
ck_<テーブル>_<ルール>
(例)ck_t_contracts_status_0_3(status が 0〜3 のみ許容)
用途
値域制限や条件を定義。
アプリケーションだけでなくDBでも保証するようにする。
NOT NULL制約
役割
値の必須性を保証。
必須カラムには必ず付ける。
アプリケーションのバリデーション任せにしない!
インデックス制約(INDEX)
命名規則例
idx_<テーブル>_<カラム>
(例)idx_t_orders_created_at
用途
検索性能の向上。
ユニーク制約と組み合わせることもある。
作りすぎると更新コストが増えるため、運用しながら最適化が必要。
まとめ
命名規則も制約も、最初はただのルールに見えるけれど、実務の中で「後から読みやすい」「間違いを防げる」という効果を実感しました。特に制約は「アプリとDBの二重チェック」でようやく堅牢な設計になる、と腑に落ちました。
結局のところ、これらは「未来の自分や他の人が困らないようにするための仕組み」なんだと思います。DB設計のルールを意識できるかどうかで、システムの安心感が大きく変わると感じました。