Query Go
命名規則の Tips — テーブル・カラム名で後悔しないための約束事
データモデル設計ガイド

命名規則の Tips — テーブル・カラム名で後悔しないための約束事

snake_case・{table}_id 主キー・is_* / *_at など、後から楽になる命名規約。

命名規則の Tips — テーブル・カラム名で後悔しないための約束事 diagram

基本は snake_case — 識別子クォートから逃げる

テーブル名・カラム名は すべて小文字 + アンダースコア区切り(snake_case) を基本とします。キャメルケース(CustomerName)やパスカルケース(customerName)を避けるのは、RDBMS 間での大文字小文字の扱いが揃っていないためです。

  • PostgreSQL: 引用符なしで書かれた識別子は自動で小文字化される → CustomerName と書いても customername として保存
  • Oracle: 同上、自動で大文字化される → CUSTOMERNAME として保存
  • MySQL: OS 依存で、Linux ではテーブル名が大文字小文字区別される / Windows では区別されない
  • SQL Server: 照合順序(collation)次第で挙動が変わる

大文字を使いたいときは "CustomerName" のように ダブルクォートで囲み続ける 必要があり、アプリ・運用クエリ・ツールすべてに波及する大きな負債になります。最初から小文字だけで書いておけば、この問題は発生しません

合わせて決めておきたいルール:

  • 数字始まりの識別子を避ける(2024_sales は多くの RDBMS で NG)
  • ハイフンを使わない(order-items は引用符必須)
  • 予約語(user, order, group, type など)は避けて users / orders / groups / type_code にする

マスタ / トランザクションに prefix — mst_ / trn_

テーブルを マスタ系(mst_)トランザクション系(trn_) で prefix を分けておくと、規模が大きくなった時に一覧性・検索性が劇的に上がります。

Prefix性質
mst_マスタ:変化が遅い参照データ。件数は少なく、FK の参照先になるmst_customers / mst_products / mst_departments
trn_トランザクション:業務で日々積まれるデータ。FK で mst_ を参照するtrn_orders / trn_order_items / trn_shipments
log_ / hst_ログ・履歴:削除せず積むだけ。監査用途log_login / hst_price_changes
rel_ / map_多対多の交差テーブル(任意)rel_user_roles

メリット:

  • ツールのテーブル一覧が役割でグルーピングされる(アルファベット順で並べるだけで整理される)
  • 運用 SQL で LIKE 'trn_%' のようにマスタを除いた集計や、バックアップ範囲の指定が楽
  • 新人が「これは参照して良いマスタか、それとも業務データか」を名前だけで判別できる

注意点・バリエーション:

  • prefix を 途中から導入すると移行が重いので、プロジェクト開始時に決めること。既存システムに後付けするのは現実的でないことが多い
  • SaaS・アプリ系では prefix を使わず素直に customers / orders とする文化もある(Rails/Laravel 系の標準はこちら)
  • 業務系・基幹系・日本の SIer 文化では M_ / T_ のように大文字 prefix も多いが、前項の通り snake_case に揃えるほうが無難
sql
-- マスタ
CREATE TABLE mst_customers (
  customer_id BIGINT PRIMARY KEY,
  name        VARCHAR(100) NOT NULL,
  tel         VARCHAR(20)
);

-- トランザクション
CREATE TABLE trn_orders (
  order_id    BIGINT PRIMARY KEY,
  customer_id BIGINT NOT NULL REFERENCES mst_customers(customer_id),
  ordered_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ログ
CREATE TABLE log_login (
  log_id      BIGSERIAL PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  logged_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

主キーは id ではなく {table}_id

多くのフレームワーク(Rails, Django 等)は慣例で主キーを id にしますが、「テーブル名 + _id」で主キーを命名する ルールにしておくと、JOIN・デバッグ・運用 SQL で一気に楽になります。

例: customers.id ではなく customers.customer_idorders.id ではなく orders.order_id

メリット:

  • JOIN 条件がシンプル: ON orders.customer_id = customers.customer_id、あるいは USING (customer_id) がそのまま使える
  • FK 側と PK 側のカラム名が一致するので、どちらを参照しているか混乱しない
  • 集計 SQL で SELECT customer_id, COUNT(*) のように書いたとき、どのテーブルの id か一目でわかる
  • SELECT id FROM orders JOIN customers ON ... のような曖昧な書き方がそもそもできず、エラーで弾ける

デメリット・反論:

  • ORM(Active Record 系)は id 固定がデフォルトで、primary_key :customer_id のような設定が必要になる場面がある
  • カラム名がわずかに冗長になる(customers.customer_id

それでも、データ量が増え・結合が増えるほど「どの id か」が明確な方が強いため、基幹系・業務系ではほぼ一貫して {table}_id が採用されます。

sql
-- {table}_id 方式なら USING が自然に使える
SELECT c.name, o.ordered_at
FROM trn_orders o
JOIN mst_customers c USING (customer_id);

-- id 固定方式だとこう書くことになる
SELECT c.name, o.ordered_at
FROM orders o
JOIN customers c ON o.customer_id = c.id;

外部キー列は「参照先の主キー名」と同じにする

前項と併せて、FK 列は参照先の PK と同名にする のを原則にします。mst_customers.customer_id を参照する FK は、どのテーブルでも customer_id

例外は同じテーブルを複数回参照するときです。その場合は役割を表す prefix を付けて区別します。

  • 荷物の発送元・発送先が両方 mst_addresses を参照: from_address_id / to_address_id
  • 承認フローで承認者と申請者が両方 mst_users を参照: applicant_user_id / approver_user_id
  • 作成者・更新者: created_by_user_id / updated_by_user_id(単に created_by でも可)

こうしておくと、カラム名を見ただけで参照先が一意に決まるため、ER 図なしでもスキーマの読解が進みます。

カラムの接頭辞・接尾辞で型・意味を表す

カラム名に意味を表す接頭辞・接尾辞を一貫して付けると、SQL を書くときに型や扱い方を名前だけで想起できます。

規則意味
is_* / has_*真偽値(BOOLEAN)。flag 単独は避けるis_active / is_deleted / has_paid
*_at時刻(TIMESTAMP / TIMESTAMPTZ)created_at / shipped_at / expires_at
*_on / *_date日付のみ(DATE)birth_on / invoice_date
*_count / *_total件数・集計済み数値order_count / amount_total
*_id他テーブルへの参照(FK) / 主キーcustomer_id / order_id
*_code業務で定まる短いコード値。区分値のキーstatus_code / currency_code
*_name人が読む名称product_name / department_name
*_type / *_kind区分・種別payment_type / item_kind
*_url / *_pathURL / ファイルパスavatar_url / upload_path

アンチパターン:

  • flag1, flag2 のような番号付きの意味不明なフラグ列(何の真偽かわからない)
  • date 単独(予約語かつ日付と時刻のどちらか曖昧)
  • name, type のようなテーブルをまたぐと衝突する汎用名customer_name / product_name のように具体的に)
  • ビジネス用語を略しすぎた cst_nm, prd_cd(古い基幹系に残る文化。可読性が大幅に下がる)

単数形 vs 複数形 — チームで 1 つに決める

テーブル名を 単数形customer, order)にするか 複数形customers, orders)にするかは、長年の宗教論争でどちらにも合理性があるため、チームで 1 つに決めて全テーブルで統一することだけが重要です。

  • 複数形派(Rails, Laravel, Web 系多数): 「テーブルは行の集合」という英文法的直感。SELECT * FROM customers は「複数の顧客を取る」で自然
  • 単数形派(Hibernate, .NET, 業務系多数): ドメインモデルのクラス名と 1:1 対応しやすく、予約語衝突しにくい(user と違って users は予約されない、という皮肉な理由で複数形派が多数)

前述の mst_ / trn_ prefix 文化とは合わせて考えます。日本の業務系では mst_customer(単数)が多く、Web 系では customers(複数)が多い傾向です。混在だけが避けるべき状態で、どちらでも成立します。

識別子の長さ制限に注意

RDBMS ごとに識別子(テーブル名・カラム名)の最大長に差があります。とくに Oracle の 30 文字制限 に引っかかると、自動生成されるインデックス名・FK 名(mst_customers_customer_id_fkey のような長い名前)が切れて衝突するトラブルが起きます。

RDBMS最大文字数備考
Oracle(〜12c)30インデックス名・FK 名を含めて注意
Oracle(12.2+)12812.2 以降で緩和
PostgreSQL63NAMEDATALEN で拡張可能(再ビルド必要)
MySQL64テーブル・カラム共通
SQL Server128ほぼ困らない
SQLite制限なし実質数千文字まで OK

Oracle サポートが視野にあるなら、テーブル名は 20 文字程度まで に収めておくと、FK・インデックス名が 30 文字内に収まって安全です。

まとめ — 一枚の規約シートに書いて共有

命名規則は一貫していれば詳細はあまり重要ではないため、最初にチームで決めて、1 枚の規約シートに書いて共有するのが最大の効果を生みます。

ミニマムで決めておくこと:

  • ケース: snake_case(小文字固定)
  • テーブル prefix: mst_ / trn_ / log_ を使うか、使わないか
  • テーブルの単複: 単数形か複数形か
  • 主キー: id{table}_id
  • 時刻列: *_at(TIMESTAMP)、*_on(DATE)
  • 真偽値: is_* / has_* 必須
  • FK 列名: 参照先 PK と同名、例外のときの prefix ルール
  • 予約語回避リスト(user, order, group, type など)

これらを最初の 1 週間で決めておけば、後から来るメンバー全員が迷わず同じスキーマを書けるようになります。途中から変えるのはほぼ不可能なので、プロジェクト開始直後が最良のタイミングです。