DBの命名規則とは、テーブル・カラム・インデックス・制約などの名前の付け方をチームでそろえるための決まりです。 名前から「何のオブジェクトで、どのテーブルのどの列に関わるか」が読み取れるようにします。

この記事では、推奨セットと接頭辞つきの命名パターンを一覧で示し、そのまま配れる規約テンプレート、規約に沿った DDL、DB 製品ごとの識別子長と大文字小文字の違い、規約違反を探す SQL をまとめます。変数名や関数名の付け方、テーブル定義書の書き方は扱わず、DB オブジェクトの名前 に絞ります。

確認環境(確認日 2026-10-06):Ubuntu 24.04、PostgreSQL 16.15、MySQL 8.0.46、Python 3.11.15+Django 5.2.17。SQL Server と Oracle Database の内容は、今回開いた公式ドキュメント(Microsoft Learn「Database identifiers」、Oracle Database 19c SQL Language Reference)の記載にもとづきます。出力例のデータベース名は appdb に置き換えています。

この記事の結論

  • 小文字のスネークケース で書く(equipment_reservations、created_at)。
  • テーブル名は 複数形か単数形のどちらかに統一 する。新しく決めるなら複数形(users)が、予約語との衝突が少なく、主要な ORM の既定とも合う。
  • 主キーは id、外部キーは 参照先の単数形_id(user_id)。
  • 制約とインデックスには pk_/fk_/uq_/ix_/ck_ の接頭辞 を付けて、自分で名前を決める。
  • 名前の長さは 63バイト以内 に収めると、PostgreSQL・MySQL・SQL Server・Oracle のどれでもそのまま使える。

DB命名規則の早見表

対象 命名パターン 例
テーブル 名詞の複数形(または単数形に統一)、スネークケース users、equipment_reservations
中間テーブル 2つのテーブル名をつなぐか、関係を表す名詞 user_roles、memberships
カラム 名詞の単数形、スネークケース display_name、email
主キー id id
外部キーのカラム 参照先の単数形_id user_id、department_id
真偽値 is_/has_/can_ + 状態 is_active、has_attachment
日時 動詞の過去分詞 + _at created_at、updated_at、canceled_at
日付 動詞の過去分詞 + _on、または名詞 + _date reserved_on、due_date
件数・数量 名詞 + _count/_qty retry_count
区分・状態 status、名詞 + _type/_code status、equipment_type
主キー制約 pk_テーブル名 pk_users
外部キー制約 fk_テーブル名_カラム名 fk_users_department_id
一意制約 uq_テーブル名_カラム名 uq_users_email
CHECK 制約 ck_テーブル名_内容 ck_equipment_reservations_period
インデックス ix_テーブル名_カラム名(複数は順に) ix_equipment_reservations_user_id_starts_at
ビュー v_ + 内容(付けない流儀もある。どちらかに統一) v_active_users
シーケンス テーブル名_カラム名_seq users_id_seq

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「DB命名規則一覧|テーブル名・カラム名・インデックス名・制約名」

推奨セットとその理由

決めること 推奨 理由
記法 小文字のスネークケース 引用符なしの名前を PostgreSQL は小文字に、Oracle は大文字にそろえる。小文字のスネークケースなら、どの製品でも引用符なしで書ける
文字 半角の英小文字・数字・アンダースコア。先頭は英字 引用符が不要で、ツールや ORM がそのまま扱える
テーブルの単複 どちらかに統一 混ざると、名前を毎回確かめることになる
主キー id 結合条件が users.id = reservations.user_id の形にそろう
外部キー 参照先の単数形_id 列名から参照先がわかる
制約・インデックス 接頭辞つきで明示 自動で付く名前は製品ごとに違う。エラーメッセージに出る名前から原因を読める
略語 原則使わない。使うものは一覧にする qty、no などの読み方が人によって変わる
予約語 避ける user、order、group は引用符が必要になる製品がある

そのまま配れる命名規約テンプレート

# データベース命名規約

## 1. 共通
- 名前は半角の英小文字・数字・アンダースコアで書く(スネークケース)。先頭は英字。
- 長さは63バイト以内。
- 予約語(user、order、group など)は使わない。
- 略語は「6. 略語一覧」にあるものだけ使う。
- 引用符が必要になる名前(大文字、空白、日本語)は使わない。

## 2. テーブル
- 名詞の【複数形/単数形】に統一する。 例:
- 中間テーブルは【2つのテーブル名をつなぐ/関係を表す名詞】。 例:
- 接頭辞(t_、m_ など)は【付けない/付ける】。

## 3. カラム
- 主キーは id。
- 外部キーは「参照先の単数形_id」。 例:
- 真偽値は is_/has_/can_ で始める。
- 日時は _at、日付は _on で終える。
- テーブル名をカラム名に繰り返さない(users.user_name ではなく users.name)。
- すべてのテーブルに created_at、updated_at を持つ。

## 4. 制約
| 種類 | パターン | 例 |
|---|---|---|
| 主キー | pk_テーブル名 | |
| 外部キー | fk_テーブル名_カラム名 | |
| 一意 | uq_テーブル名_カラム名 | |
| CHECK | ck_テーブル名_内容 | |

## 5. インデックス
- ix_テーブル名_カラム名(複数カラムは定義の順に並べる)。
- 63バイトを超えるときは【テーブル名を略語一覧の形に縮める】。

## 6. 略語一覧
| 略語 | 元の語 |
|---|---|
| | |

## 7. 例外
- 既存のテーブルは現在の名前のままにし、新しく作るものからこの規約に従う。

記入例(架空の備品予約システム)

項目 決めた内容
テーブルの単複 複数形(users、departments、equipment_reservations)
中間テーブル 2つのテーブル名をつなぐ(user_roles)
テーブルの接頭辞 付けない
外部キー user_id、department_id、equipment_id
長い名前の縮め方 equipment_reservations は eq_rsv と書いてよい(インデックス名と制約名に限る)
略語一覧 eq=equipment、rsv=reservation、qty=quantity

表計算ソフトに貼るときは、次のタブ区切りを使います。

対象  パターン    例
テーブル    名詞の複数形  equipment_reservations
主キー id  id
外部キーのカラム    参照先の単数形_id  user_id
主キー制約   pk_テーブル名    pk_equipment_reservations
外部キー制約  fk_テーブル名_カラム名   fk_equipment_reservations_user_id
一意制約    uq_テーブル名_カラム名   uq_users_email
CHECK制約 ck_テーブル名_内容 ck_equipment_reservations_period
インデックス  ix_テーブル名_カラム名   ix_equipment_reservations_user_id_starts_at

規約に沿ったCREATE TABLEとCREATE INDEXの例

PostgreSQL 16 で実行した DDL です。

-- 規約に沿った DDL(PostgreSQL 16)
CREATE TABLE departments (
    id         bigint GENERATED ALWAYS AS IDENTITY,
    name       varchar(100) NOT NULL,
    created_at timestamptz  NOT NULL DEFAULT now(),
    CONSTRAINT pk_departments PRIMARY KEY (id),
    CONSTRAINT uq_departments_name UNIQUE (name)
);

CREATE TABLE users (
    id            bigint GENERATED ALWAYS AS IDENTITY,
    department_id bigint       NOT NULL,
    email         varchar(254) NOT NULL,
    display_name  varchar(100) NOT NULL,
    is_active     boolean      NOT NULL DEFAULT true,
    created_at    timestamptz  NOT NULL DEFAULT now(),
    updated_at    timestamptz  NOT NULL DEFAULT now(),
    CONSTRAINT pk_users PRIMARY KEY (id),
    CONSTRAINT uq_users_email UNIQUE (email),
    CONSTRAINT fk_users_department_id FOREIGN KEY (department_id) REFERENCES departments (id)
);

CREATE TABLE equipment_reservations (
    id           bigint GENERATED ALWAYS AS IDENTITY,
    user_id      bigint      NOT NULL,
    equipment_id bigint      NOT NULL,
    starts_at    timestamptz NOT NULL,
    ends_at      timestamptz NOT NULL,
    reserved_on  date        NOT NULL,
    status       varchar(20) NOT NULL DEFAULT 'reserved',
    CONSTRAINT pk_equipment_reservations PRIMARY KEY (id),
    CONSTRAINT fk_equipment_reservations_user_id FOREIGN KEY (user_id) REFERENCES users (id),
    CONSTRAINT ck_equipment_reservations_period CHECK (starts_at < ends_at),
    CONSTRAINT ck_equipment_reservations_status CHECK (status IN ('reserved', 'canceled', 'returned'))
);

CREATE INDEX ix_users_department_id ON users (department_id);
CREATE INDEX ix_equipment_reservations_user_id_starts_at ON equipment_reservations (user_id, starts_at);

作成後に、制約とインデックスの名前を一覧にした結果です。

SELECT conrelid::regclass AS table_name, conname, contype
  FROM pg_constraint
 WHERE connamespace = 'public'::regnamespace
 ORDER BY 1, 3, 2;
       table_name       |              conname              | contype 
------------------------+-----------------------------------+---------
 departments            | pk_departments                    | p
 departments            | uq_departments_name               | u
 users                  | fk_users_department_id            | f
 users                  | pk_users                          | p
 users                  | uq_users_email                    | u
 equipment_reservations | ck_equipment_reservations_period  | c
 equipment_reservations | ck_equipment_reservations_status  | c
 equipment_reservations | fk_equipment_reservations_user_id | f
 equipment_reservations | pk_equipment_reservations         | p
(9 rows)

contype は p=主キー、f=外部キー、u=一意、c=CHECK です。接頭辞と一致しているので、名前だけで種類がわかります。

名前を付けなかったときに自動で付く名前

制約やインデックスの名前を省略すると、製品ごとに違う名前が付きます。同じ内容のテーブルを、名前を省略して作って確かめました。

CREATE TABLE auto_named (
    id      bigint PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES users (id),
    code    varchar(10) UNIQUE,
    qty     int CHECK (qty > 0)
);
CREATE INDEX ON auto_named (user_id, code);   -- PostgreSQL のみ。名前なしで作れる

MySQL では外部キーをテーブル制約(FOREIGN KEY (user_id) REFERENCES users (id))として書いて実行しています。

種類 PostgreSQL 16 で付いた名前 MySQL 8.0 で付いた名前
主キー auto_named_pkey PRIMARY
外部キー auto_named_user_id_fkey auto_named_ibfk_1
一意制約 auto_named_code_key code
CHECK 制約 auto_named_qty_check auto_named_chk_1
インデックス auto_named_user_id_code_idx (外部キーの列に user_id という名前で作られた)

MySQL の _ibfk_1 や _chk_1 は連番なので、名前から対象の列がわかりません。名前を明示しておくと、制約違反のエラーメッセージに出る名前から、どの列のどの決まりに当たったかを読み取れます。

識別子の長さと大文字小文字の違い(製品別)

製品 最大長 超えたとき 引用符なしの名前の扱い 出典・確認方法
PostgreSQL 16 63バイト NOTICE を出して切り詰める 小文字にそろえる 実行して確認
MySQL 8.0 64文字 ERROR 1059 で止まる テーブル名は OS に依存(Linux では区別する)。カラム名・インデックス名は区別しない 実行して確認(Linux)
SQL Server 128文字(ローカル一時テーブルは116文字) — データベースの照合順序による Microsoft Learn「Database identifiers」
Oracle Database 19c 128バイト(COMPATIBLE が 12.2 以上のとき。それより低い設定では30バイト) — 大文字として解釈する Oracle Database 19c SQL Language Reference

4製品で共通して使うなら、いちばん短い PostgreSQL の63バイトに合わせる のが確実です。Oracle で COMPATIBLE が 12.2 より低い環境も対象にする場合は、30バイトが上限になります。

PostgreSQL:63バイトを超えると切り詰められる

64文字のインデックス名を指定した結果です。

CREATE INDEX ix_equipment_reservations_user_id_equipment_id_starts_at_ends_at
    ON equipment_reservations (user_id, equipment_id, starts_at, ends_at);
NOTICE:  identifier "ix_equipment_reservations_user_id_equipment_id_starts_at_ends_at" will be truncated to "ix_equipment_reservations_user_id_equipment_id_starts_at_ends_a"
CREATE INDEX

エラーにはならず、末尾が1文字欠けた名前で作られます。続けて、先頭63バイトが同じになる別の名前を指定すると、切り詰めたあとの名前が重なって止まりました。

CREATE INDEX ix_equipment_reservations_user_id_equipment_id_starts_at_ends_at_status
    ON equipment_reservations (user_id, equipment_id, starts_at, ends_at, status);
NOTICE:  identifier "ix_equipment_reservations_user_id_equipment_id_starts_at_ends_at_status" will be truncated to "ix_equipment_reservations_user_id_equipment_id_starts_at_ends_a"
ERROR:  relation "ix_equipment_reservations_user_id_equipment_id_starts_at_ends_a" already exists

上限は文字数ではなくバイト数です。日本語(UTF-8 で1文字3バイト)の27文字の名前は、21文字に切り詰められました。

NOTICE:  identifier "備品予約の履歴を保存するためのテーブルその一二三四五六" will be truncated to "備品予約の履歴を保存するためのテーブルその"

                 tablename                  | chars | bytes 
--------------------------------------------+-------+-------
 備品予約の履歴を保存するためのテーブルその |    21 |    63

MySQL:64文字を超えるとエラーで止まる

65文字のインデックス名を指定した結果です。

ERROR 1059 (42000): Identifier name 'ix_users_department_id_display_name_is_active_created_at_updated_' is too long

64文字ちょうどの名前(ix_users_department_id_display_name_is_active_created_at_updated)は作成できました。MySQL の上限は文字数で数えるため、PostgreSQL で切り詰められた日本語27文字(81バイト)のテーブル名は、そのまま作成できます。

+--------------------------------------------------------+-------+-------+
| TABLE_NAME                                             | chars | bytes |
+--------------------------------------------------------+-------+-------+
| 備品予約の履歴を保存するためのテーブルその一二三四五六 |    27 |    81 |
+--------------------------------------------------------+-------+-------+

MySQL のリファレンスには、名前を省略した制約はテーブル名から生成され(テーブル名_ibfk_番号、テーブル名_chk_番号)、テーブル名が64文字に近いと生成された名前が上限を超えてエラーになる、という注意も書かれています。

大文字小文字:PostgreSQLは小文字にそろえる

CREATE TABLE MixedCase (Id int, "UserName" text);
SELECT table_name, column_name FROM information_schema.columns
 WHERE table_name ILIKE 'mixedcase' ORDER BY ordinal_position;
 table_name | column_name 
------------+-------------
 mixedcase  | id
 mixedcase  | UserName

引用符なしの MixedCase と Id は小文字で保存され、引用符つきの "UserName" だけが大文字を保っています。この列は、以後ずっと引用符つきで書く必要があります。

=> SELECT username FROM mixedcase;
ERROR:  column "username" does not exist
HINT:  Perhaps you meant to reference the column "mixedcase.UserName".

さらに CREATE TABLE "MixedCase" (id int); も通り、mixedcase と MixedCase という2つのテーブルが同時に存在する状態になりました。

大文字小文字:MySQLはテーブル名だけOSに依存する

Linux 上の MySQL 8.0(lower_case_table_names は 0)で実行した結果です。

CREATE TABLE MixedCase (Id int, UserName text);
SELECT id, username FROM MixedCase;   -- カラム名は大文字小文字を区別しない
SELECT id FROM mixedcase;             -- テーブル名は区別する
Empty set

ERROR 1146 (42S02): Table 'appdb.mixedcase' doesn't exist

MySQL のリファレンスによると、lower_case_table_names の既定値は Unix 系で 0、Windows で 1、macOS で 2 で、サーバーの初期化時にしか設定できません。リファレンスも、データベース名とテーブル名は常に小文字で作って参照する ことを勧めています。開発用の PC と本番のサーバーで OS が違っても、小文字で統一していれば同じ SQL が動きます。

MySQLでの接頭辞の注意点

同じ規約の DDL を MySQL 8.0 で実行し、付いた名前を確かめました。

+-------------+------------------------+-----------------+
| TABLE_NAME  | CONSTRAINT_NAME        | CONSTRAINT_TYPE |
+-------------+------------------------+-----------------+
| departments | PRIMARY                | PRIMARY KEY     |
| departments | uq_departments_name    | UNIQUE          |
| users       | ck_users_email         | CHECK           |
| users       | fk_users_department_id | FOREIGN KEY     |
| users       | PRIMARY                | PRIMARY KEY     |
| users       | uq_users_email         | UNIQUE          |
+-------------+------------------------+-----------------+
+-------------+------------------------+---------------+
| TABLE_NAME  | INDEX_NAME             | cols          |
+-------------+------------------------+---------------+
| departments | PRIMARY                | id            |
| departments | uq_departments_name    | name          |
| users       | fk_users_department_id | department_id |
| users       | ix_users_display_name  | display_name  |
| users       | PRIMARY                | id            |
| users       | uq_users_email         | email         |
+-------------+------------------------+---------------+
  • CONSTRAINT pk_users PRIMARY KEY (id) と書いても、主キーの名前は PRIMARY になる。規約の pk_ は MySQL では「書いても反映されない」と注記しておく。
  • 外部キーを作ると、外部キーと同じ名前のインデックス(fk_users_department_id)が自動で作られる。同じ列に ix_ のインデックスを別に作る必要はない。

単数形と複数形のどちらにするか

観点 複数形(users) 単数形(user)
考え方 テーブルは行の集まり 1行が表すものの名前
予約語との衝突 少ない(users、orders、groups) user、order、group が当たりやすい
SQL の読みやすさ SELECT ... FROM users が自然 user.name のように列の修飾が自然
外部キーとの対応 users.id ← user_id(単複の変換が要る) user.id ← user_id(そのまま対応)
不規則な複数形 person→people、category→categories を決める必要がある 考えなくてよい
クラス名との対応 ORM が変換する前提 クラス名とそのまま一致

ORMの既定値との対応

ORM・フレームワーク クラス EquipmentReservation の既定のテーブル名 単複 出典・確認方法
Ruby on Rails(Active Record) equipment_reservations 複数形 Rails ガイド 8.1「Active Record Basics」
Laravel(Eloquent) equipment_reservations 複数形 Laravel 12.x ドキュメント「Eloquent」
Spring Boot(Hibernate) equipment_reservation クラス名のまま(単数形) Spring Boot 4.1.1「Data Access」
Django アプリ名_equipmentreservation クラス名のまま(単数形、区切りなし) Django 5.2.17 で実行して確認

Rails と Laravel は「クラス名を複数形のスネークケースにしたもの」をテーブル名とみなし、主キーは id、外部キーは 単数形_id を前提にします。Spring Boot は、既定でキャメルケースをアンダースコア区切りの小文字に変換します(TelephoneNumber は telephone_number)。

Django で確かめた結果は次のとおりです(アプリ名は booking)。

Django 5.2.17
Department -> booking_department ['id', 'name']
EquipmentReservation -> booking_equipmentreservation ['id', 'department_id', 'starts_at']
CREATE INDEX "booking_equipmentreservation_department_id_f5766e9a" ON "booking_equipmentreservation" ("department_id");

テーブル名は「アプリ名_クラス名の小文字」で、単語の区切りは入りません。外部キーの列は department_id、インデックス名は末尾に英数字8文字が付く形でした。

選び方の判断基準

条件 選ぶもの
既存のテーブルがある 既存に合わせる
Rails か Laravel を使う 複数形(既定のまま使える)
Spring Boot(JPA)や Django を使い、既定の名前を活かす 単数形
ORM を使わず SQL を直接書く、または複数の言語から使う 複数形(予約語を避けやすい)
英語の不規則な複数形で迷いたくない 単数形

どちらを選んでも、混ぜないこと がいちばん効きます。

規約違反を探すSQL(PostgreSQL)

information_schema とシステムカタログから、規約に合わない名前を一覧にします。

-- 1) 小文字のスネークケースになっていないテーブル名・カラム名
SELECT table_name, column_name
  FROM information_schema.columns
 WHERE table_schema = 'public'
   AND (table_name !~ '^[a-z][a-z0-9_]*$' OR column_name !~ '^[a-z][a-z0-9_]*$')
 ORDER BY 1, 2;

-- 2) 接頭辞が規約どおりでない制約名
SELECT table_name, constraint_name, constraint_type
  FROM information_schema.table_constraints
 WHERE table_schema = 'public'
   AND constraint_type IN ('PRIMARY KEY', 'FOREIGN KEY', 'UNIQUE', 'CHECK')
   AND constraint_name !~ '^[0-9]+_[0-9]+_[0-9]+_not_null$'
   AND constraint_name !~ CASE constraint_type
         WHEN 'PRIMARY KEY' THEN '^pk_'
         WHEN 'FOREIGN KEY' THEN '^fk_'
         WHEN 'UNIQUE'      THEN '^uq_'
         WHEN 'CHECK'       THEN '^ck_' END
 ORDER BY 1, 2;

-- 3) 接頭辞が ix_ でないインデックス(主キー・一意制約が作るものは除く)
SELECT i.tablename, i.indexname
  FROM pg_indexes i
 WHERE i.schemaname = 'public'
   AND i.indexname !~ '^ix_'
   AND NOT EXISTS (SELECT 1 FROM pg_constraint c WHERE c.conname = i.indexname)
 ORDER BY 1, 2;

-- 4) 「_id」で終わるのに外部キー制約が無いカラム
SELECT c.table_name, c.column_name
  FROM information_schema.columns c
 WHERE c.table_schema = 'public'
   AND c.column_name LIKE '%\_id'
   AND NOT EXISTS (
         SELECT 1
           FROM information_schema.key_column_usage k
           JOIN information_schema.table_constraints t
             ON t.constraint_name = k.constraint_name AND t.table_schema = k.table_schema
          WHERE t.constraint_type = 'FOREIGN KEY'
            AND k.table_schema = c.table_schema
            AND k.table_name = c.table_name
            AND k.column_name = c.column_name)
 ORDER BY 1, 2;

規約どおりの3テーブルに加えて、自動命名の auto_named、大文字入りの MixedCase、日本語のテーブルがある状態で実行した結果です。

                 table_name                 | column_name 
--------------------------------------------+-------------
 MixedCase                                  | id
 mixedcase                                  | UserName
 備品予約の履歴を保存するためのテーブルその | id
(3 rows)

 table_name |     constraint_name     | constraint_type 
------------+-------------------------+-----------------
 auto_named | auto_named_code_key     | UNIQUE
 auto_named | auto_named_pkey         | PRIMARY KEY
 auto_named | auto_named_qty_check    | CHECK
 auto_named | auto_named_user_id_fkey | FOREIGN KEY
(4 rows)

 tablename  |          indexname          
------------+-----------------------------
 auto_named | auto_named_user_id_code_idx
(1 row)

       table_name       | column_name  
------------------------+--------------
 equipment_reservations | equipment_id
(1 row)

4つ目の問い合わせは、equipment_id という列があるのに外部キー制約が無いことを見つけています。命名規則があると、名前と実際の定義のずれを機械的に探せる ようになります。

2つ目の問い合わせの _not_null で終わる名前を除く条件は、PostgreSQL 16 の information_schema.table_constraints に NOT NULL 制約が CHECK として数字の名前で並ぶためです。

MySQL版

-- 小文字のスネークケースになっていないテーブル名・カラム名('c' は大文字小文字を区別する指定)
SELECT table_name, column_name FROM information_schema.columns
 WHERE table_schema = 'appdb'
   AND (NOT REGEXP_LIKE(table_name, '^[a-z][a-z0-9_]*$', 'c')
     OR NOT REGEXP_LIKE(column_name, '^[a-z][a-z0-9_]*$', 'c'))
 ORDER BY 1, 2;

-- 接頭辞が規約どおりでない制約名(主キーは常に PRIMARY なので対象外)
SELECT table_name, constraint_name, constraint_type FROM information_schema.table_constraints
 WHERE table_schema = 'appdb'
   AND ( (constraint_type = 'FOREIGN KEY' AND constraint_name NOT LIKE 'fk\_%')
      OR (constraint_type = 'UNIQUE'      AND constraint_name NOT LIKE 'uq\_%')
      OR (constraint_type = 'CHECK'       AND constraint_name NOT LIKE 'ck\_%') )
 ORDER BY 1, 2;
+------------+-------------+
| TABLE_NAME | COLUMN_NAME |
+------------+-------------+
| MixedCase  | Id          |
| MixedCase  | UserName    |
+------------+-------------+

+------------+-------------------+-----------------+
| TABLE_NAME | CONSTRAINT_NAME   | CONSTRAINT_TYPE |
+------------+-------------------+-----------------+
| auto_named | auto_named_chk_1  | CHECK           |
| auto_named | auto_named_ibfk_1 | FOREIGN KEY     |
| auto_named | code              | UNIQUE          |
+------------+-------------------+-----------------+

確かめて分かったこと

  • PostgreSQL は 63バイトを超えてもエラーにせず、NOTICE を出して切り詰める。マイグレーションのログで NOTICE を見ていないと気づけない。
  • 切り詰めた結果が既存の名前と重なると、そこで初めて「already exists」で止まる。ix_テーブル名_列… の形は先頭が同じになるため、長いテーブル名ほど重なりやすい。
  • PostgreSQL の上限はバイト数、MySQL の上限は文字数。日本語27文字の名前は、PostgreSQL では21文字に切り詰められ、MySQL ではそのまま作成できた。
  • MySQL では 主キー制約に付けた名前は使われず、常に PRIMARY になる。
  • MySQL は外部キーを作ると 同じ名前のインデックスを自動で作る。
  • PostgreSQL では、引用符つきで作った "MixedCase" と引用符なしの mixedcase が 別のテーブルとして共存 できる。
  • PostgreSQL で CREATE TABLE user (id int); を実行すると syntax error at or near "user" で止まった(order、group も同じ)。引用符で囲めば作れるが、以後も引用符が必要になる。
  • Linux 上の MySQL は、テーブル名の大文字小文字を区別し、カラム名は区別しない。同じ SQL でも対象によって結果が分かれる。

つまずきやすい点と直し方

状況 起きること 直し方
ORM やツールが引用符つきで大文字入りの名前を作った 以後の SQL すべてに引用符が必要になる 作成時に小文字のスネークケースを指定する。既存のものは ALTER TABLE ... RENAME で直す
インデックス名が63バイトを超える PostgreSQL で切り詰められ、別の名前と重なる 規約に略語一覧を持ち、テーブル名の部分を縮める
制約名を省略している 製品ごとに名前が変わり、マイグレーションで名前を指定して削除できない CONSTRAINT 名前 を必ず書く
テーブル名に user や order を使う 引用符が必要になる製品がある 複数形にするか、app_users のように語を足す
users.user_name のようにテーブル名を繰り返す 結合したときに冗長になる users.name とし、必要なら問い合わせ側で別名を付ける
外部キーの列名が参照先と対応していない(owner、uid) 参照先を定義から調べることになる owner_user_id のように、役割 + 参照先の単数形 + _id にする

よくある質問

Q. テーブル名は単数形と複数形のどちらがよいですか? A. どちらかに統一されていれば、どちらでも成り立ちます。新しく決めるなら、予約語を避けやすく Rails や Laravel の既定とも合う複数形が無難です。Spring Boot や Django の既定の名前を活かすなら単数形が合います。

Q. インデックス名と制約名の付け方は? A. 種類の接頭辞_テーブル名_カラム名 の形にします。主キーは pk_、外部キーは fk_、一意制約は uq_、CHECK は ck_、インデックスは ix_ です。

Q. テーブル名やカラム名は何文字まで使えますか? A. PostgreSQL は63バイト、MySQL は64文字、SQL Server は128文字、Oracle Database は128バイト(COMPATIBLE が 12.2 以上)です。複数の製品で使うなら63バイト以内に収めます。

Q. テーブル名に t_ や m_ の接頭辞は付けたほうがよいですか? A. 新しく決めるなら付けなくても困りません。種類は information_schema で調べられ、テーブルの役割が変わったときに名前を変えずに済みます。既存のシステムで付いているなら、そのままそろえます。

Q. カラム名が大文字で作られてしまったときはどうすればよいですか? A. ALTER TABLE テーブル名 RENAME COLUMN "UserName" TO user_name; のように、小文字のスネークケースへ名前を変えます。変更前に、その列を参照しているアプリやビューを確認します。

まとめ

  • 小文字のスネークケース、テーブルは 単複どちらかに統一、主キーは id、外部キーは 参照先の単数形_id。
  • 制約とインデックスは pk_/fk_/uq_/ix_/ck_ の接頭辞で名前を明示する。
  • 長さは 63バイト以内。PostgreSQL は超えた分を切り詰め、MySQL はエラーで止める。
  • 引用符が必要になる名前を作らない。大文字小文字の扱いは製品と OS で変わる。
  • 単複は 使う ORM の既定 に合わせると、設定が少なく済む。
  • 規約は information_schema の問い合わせで確かめられる形 にしておく。

参考・出典

  • PostgreSQL 16 ドキュメント「4.1. Lexical Structure」(postgresql.org、2026-10-06 確認)
  • MySQL 8.0 リファレンスマニュアル「Identifier Length Limits」「Identifier Case Sensitivity」(dev.mysql.com)
  • Microsoft Learn「Database identifiers」(learn.microsoft.com)
  • Oracle Database 19c SQL Language Reference「Database Object Names and Qualifiers」(docs.oracle.com)
  • Rails ガイド 8.1「Active Record Basics」(guides.rubyonrails.org)
  • Laravel 12.x ドキュメント「Eloquent: Getting Started」(laravel.com)
  • Spring Boot 4.1.1 リファレンス「How-to: Data Access」(docs.spring.io)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「DB命名規則一覧|テーブル名・カラム名・インデックス名・制約名」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?