はじめに
データベース設計の品質は、システムの保守性や拡張性に大きな影響を与えます。
特に「命名規則」と「制約の付け方」は、プロジェクト内で統一されていないと、後からデータの意味や関係を理解するのが難しくなります。
本記事では、現場でよく用いられる命名のルールと制約の設計パターンを整理します。
テーブル名のプレフィックス
マスタテーブル (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設計のルールを意識できるかどうかで、システムの安心感が大きく変わると感じました。