Communitygithub.com

PostgreSQL 命名規則とスキーマ規約

PostgreSQLのテーブル、カラム、キー、制約、インデックス、リレーション、型に関する命名規則とスキーマ規約です。.NET/EF Core、Node.js(Drizzle、Prisma、TypeORM、Knex)、Go(sqlc)向け。スキーマ設計やレビュー、マイグレーション作成、エンティティ設定、sqlcクエリやSQL記述時、またはデータベースオブジェクトの命名方法を尋ねられた際に使用します。規約からの逸脱は、静かに変更せずにユーザーに報告します。

PostgreSQL 命名規則とスキーマ規約 とは?

このスキルは、PostgreSQLデータベースのオブジェクト(テーブル、カラム、制約、インデックス、リレーション、型など)に対する一貫した命名規則とスキーマ設計のベストプラクティスを提供します。主に2つのモードで動作します。Mode A(ユーザーが命名を委ねた場合)では直接規約を適用し、Mode B(既存の命名がある場合)では逸脱を報告してユーザーの確認を求めます。対応するORM/フレームワークは、EF Core(Npgsql)、Drizzle、Prisma、TypeORM、Knex、Goのsqlcで、それぞれに応じたsnake_case設定方法も含まれています。特に「Must fix」(バグやクエリ破壊を引き起こすもの)と「Recommended」(スタイルと一貫性)の区別を明確にし、既存のマイグレーションやデータベースへの変更は新しいマイグレーションとして実装するよう促します。また、Goのsqlc向けには、クエリ命名パターン(:one、:many、:execrowsなど)やパラメータ指定方法、embedの活用など、SQLファーストの開発フローに特化したガイダンスも含まれています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/EgoDZB/claude-skills/tree/HEAD/skills/postgres-conventions

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

PostgreSQL 規約

適用方法

2つのモードがあります。何かを書く前にどちらが該当するかを判断してください。

モード A — あなたが名前を決める。 ユーザーがスキーマ、エンティティ、マイグレーションを依頼し、名前を指定しなかった場合。以下の規約を直接適用します。

モード B — 名前がすでに存在するか、指定されている。 ユーザーが名前を指定した場合、または既存のコード、マイグレーション、データベースにこれらの規約から逸脱する名前が含まれている場合。決して静かにリネームせず、報告して確認を取ってください。

変更前の報告形式(ユーザーの言語で):

命名:
Must fix
- `"UserProfile"` (テーブル) → `user_profiles` — 引用符付きPascalCaseは以後すべてのクエリで引用が必要
- `created` timestamp DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC') → `created_at timestamptz DEFAULT now()` — セッションのTimeZoneがUTCでない場合、値が変動します
推奨
- `users.user_email` → `users.email` — テーブル名がすでにコンテキストを提供

次に1つ質問します:すべて適用、「Must fix」のみ適用、または現状維持。回答を待ってください。

報告ルール:

  • Must fix = バグ、クエリ破壊、ツールの問題を引き起こすもの:引用符付きの混合ケース識別子、予約語、63バイトを超える名前、タイムゾーンなしのtimestamp、誤った時刻デフォルト値、jsonb/numericが必要なデータに対するjson/float/money、FKインデックス欠落、トランザクション外のロック。
  • 推奨 = スタイルと一貫性。
  • 現在のタスクで触れるオブジェクトのみ報告してください。他の場所で逸脱に気づいた場合、「<場所>にも同様の逸脱がN件 — 一覧表示しますか?」と1行追加します。
  • 適用済みマイグレーションまたは実稼働データベース内のオブジェクト:リネームは破壊的変更です。提案のみ行い、承認された場合は新しいマイグレーションとして実装します。適用済みマイグレーションを編集しないでください。
  • コードベースが一貫して異なる規約を使用している場合、その規約が優先されます。その違いを一度だけ指摘し、以降はプロジェクトの規約に従い、各出現をフラグ付けしないでください。
  • ORMの命名規約(例:EFCore.NamingConventions)で生成された名前はそのまま受け入れられます。
  • ユーザーが規約外の名前を確認したら、それを使用し、このセッションで再度指摘しないでください。

一般ルール

  • snake_case、小文字、引用符なしの識別子。
  • 識別子は最大63バイト。それより長い名前は静かに切り詰められます。単に切り詰めるのではなく、意味を保ったまま短縮してください。
  • テーブル名は複数形:users、orders、order_items。
  • カラム名は単数形、テーブル接頭辞なし:users.email、users.user_email ではありません。
  • 一般的な略語を除き、完全な単語を使用:id、url、ip、sku。
  • 予約語や曖昧な単語を名前として使用しない:user、order、group、table、limit、offset、desc、end、date、time、timestamp。
  • 型やオブジェクトの接頭辞なし:tbl_users、str_name は禁止。
  • ブール値は肯定形:is_deleted、is_not_deleted は禁止。

標準カラム

用途名前型
主キーidbigint generated always as identity または uuid v7
作成日/更新日created_at、updated_attimestamptz not null default now()
ソフトデリートdeleted_attimestamptz null
監査created_by、updated_byusers.id へのFK
フラグis_active、is_verified、has_children、can_editboolean not null default false
ステータスstatustext + チェック制約、列挙型、またはルックアップテーブル
日付のみbirth_date、due_ondate
有効期間expires_at、valid_from、valid_totimestamptz
金額amount、unit_price、total_amount + currency_codenumeric(19,4) + char(3)、または amount_minor bigint
カウンターitems_count、retry_countinteger / bigint
順序sort_orderinteger
業務キーcode、slug、external_idtext + ユニーク
テナントtenant_idFK
楽観的ロックversion またはシステム xmininteger / xid
自由形式データmetadata、payload、settingsjsonb

接尾辞には意味があります:_at は瞬間、_on/_date はカレンダー日付、_id は参照、_count はカウンター、_url、_path、_hash。

キーと制約

オブジェクトパターン例
主キーpk_<テーブル>pk_orders
外部キーfk_<テーブル>_<参照テーブル>_<カラム>fk_orders_customers_customer_id
ユニーク制約uq_<テーブル>_<カラム>uq_users_email
代替キー (EF Core)ak_<テーブル>_<カラム>ak_products_sku
チェック制約ck_<テーブル>_<ルール>ck_orders_amount_positive
除外制約ex_<テーブル>_<カラム>ex_bookings_room_id_period
インデックスix_<テーブル>_<カラム>ix_orders_customer_id_created_at
ユニークインデックスux_<テーブル>_<カラム>ux_users_email
部分インデックスix_<テーブル>_<カラム>_<条件>ix_users_email_not_deleted
メソッド別インデックスix_<テーブル>_<カラム>_<メソッド>ix_products_tags_gin
トリガーtrg_<テーブル>_<アクション>trg_orders_set_updated_at
トリガー関数動詞 + オブジェクトset_updated_at()
シーケンスPostgreSQL デフォルトorders_id_seq
  • FKカラム:<参照テーブル(単数形)>_id、または役割が重要な場合は役割名:author_id、sender_id、recipient_id、parent_id。
  • FKパターンにはカラムが含まれるため、1つのテーブルへの複数のFKは一意になります:fk_messages_users_sender_id、fk_messages_users_recipient_id。
  • PostgreSQLはFKカラムにインデックスを作成しません。すべてのFKには、複合インデックスが既にそのカラムで始まっていない限り、インデックスが必要です。EF Coreは規約でこれらを作成しますが、Drizzle、Prisma、TypeORM、Knex、sqlcは作成しません。

リレーション

種類形状例
一対多子にFKorder_items.order_id → orders.id
一対一FK + ユニーク、または PK = FKuser_profiles.user_id
多対多結合テーブル <a>_<b>、複合PKuser_roles (user_id, role_id)、pk_user_roles
属性付き多対多意味のあるエンティティ名memberships、enrollments
自己参照parent_id または役割名categories.parent_id、employees.manager_id

ポリモーフィックペア(commentable_type + commentable_id)はFKを持つことができません。代わりに、正確に1つが設定されることをチェック制約で保証する別々のNULL許容FKカラム、または別々のテーブルを推奨します。

スキーマ、ビュー、関数、型

オブジェクトパターン例
スキーマドメインまたはモジュールauth、billing、catalog、audit
ビューv_<名前>v_active_users
マテリアライズドビューmv_<名前>mv_daily_sales
関数 / プロシージャ動詞 + オブジェクトget_user_balance、calculate_order_total
列挙型<エンティティ>_<属性>order_status
列挙値小文字のsnake_casepending、paid、refunded
データベースロール<アプリ>_<アクセス>shop_app_rw、shop_app_ro、shop_migrator

列挙値は型を再作成せずに削除できません。変更される可能性のあるセットには、text + チェック制約またはルックアップテーブルを推奨します。

型とデフォルト値

  • 瞬間を表すにはtimestamptz、デフォルトはnow()。timestamptzカラムに対するCURRENT_TIMESTAMP AT TIME ZONE 'UTC'はバグです。
  • 長さがビジネスルールでない限り、varchar(n)の代わりにtextを使用。長さ制約が必要な場合はcheck (char_length(col) <= n)を推奨。
  • jsonb、決してjsonを使用しない。
  • 金額にはnumeric、決してreal、double precision、moneyを使用しない。
  • 新しいIDカラムにはgenerated always as identityを使用し、serial/bigserialは使用しない。
  • UUID主キーにはv7を使用:PostgreSQL 18 uuidv7()、.NET 9 Guid.CreateVersion7()、Node uuidパッケージ v7()、Go github.com/google/uuid uuid.NewV7()。ランダムなv4キーはB-treeインデックスを断片化します。
  • gen_random_uuid() はPostgreSQL 13以降に組み込まれています。そのためにuuid-osspやpgcryptoは必要ありません。
  • 大文字小文字を区別しない一意性にはcitextまたはlower(email)のユニークインデックスを使用。

並行性とクエリパターン

  • 楽観的並行性制御、EF Core + Npgsql:IsRowVersion()で設定されたuintプロパティはxminにマップされます。
  • 楽観的並行性制御、NodeおよびGo:version integer not null default 0、where id = $1 and version = $2およびversion = version + 1で更新。影響を受けた行が0件の場合は競合を意味します。sqlcでは:execrowsを使用して影響を受けた行数を取得します。
  • select ... for update および for update skip locked は明示的なトランザクション内でのみ使用。自動コミットではロックは即座に解放されます。
  • アドバイザリロック:トランザクション内でpg_try_advisory_xact_lock。セッションレベルのpg_advisory_lockはアンロックまで保持される専用コネクションでのみ使用。コネクションプーリングでは、ロックとアンロックが異なるコネクションに行われる可能性があります。
  • 大規模または無限リストにはキーセットページネーションを使用:where (created_at, id) < ($1, $2) order by created_at desc, id desc limit $3。offsetは小規模で上限のあるリストのみ。
  • 将来のテーブルに対する権限は、grant ... on all tablesだけでなく、alter default privilegesが必要です。

ORMのsnake_case設定

スタック設定
EF Core + NpgsqlEFCore.NamingConventions、UseSnakeCaseNamingConvention()
Drizzlecasing: 'snake_case'
Prismaフィールドに@map、モデルに@@map
TypeORMtypeorm-naming-strategiesのSnakeNamingStrategy
Knex / ObjectionknexSnakeCaseMappers()
Go + sqlc不要。Goの名前はsnake_caseから自動生成されます

プロジェクトがsnake_case設定なしでORMを使用している場合、最初のマイグレーション前であればMust fixとして報告。マイグレーションが存在する場合は、マイグレーションコストを含めて推奨として報告します。

Go + sqlc

sqlcはSQLファーストです。スキーマはマイグレーションファイルから取得されるため、上記のすべてのルールが直接適用されます。

  • sqlc.yamlのschemaをマイグレーションディレクトリに指定します(goose、golang-migrate、dbmate、atlas形式をサポート。ダウンマイグレーションは無視されます)。マイグレーションの横に別途手書きのスキーマファイルを保持しないでください。
  • 複数形のsnake_caseテーブルは、単数形のGo構造体を生成します:order_items → OrderItem。列挙型order_status → 型OrderStatusと定数OrderStatusPending。
  • sql_package: pgx/v5を使用します。uuid → github.com/google/uuid.UUID、必要に応じてtimestamptz → time.Time(pgtypeラッパーの代わり)をオーバーライドします。
  • 頭字語はrename(url: URL、ip: IP)で修正します。カラム名をリネームしないでください。
  • クエリ名はPascalCaseの動詞 + オブジェクトで、結果の種類を含めます:
目的例
1行-- name: GetUserByEmail :one
複数行-- name: ListOrdersByCustomer :many
挿入-- name: CreateOrder :one + returning *
更新-- name: UpdateOrderStatus :execrows
削除-- name: DeleteSession :exec
一括ロード-- name: CreateOrderItems :copyfrom
  • パラメータは@nameまたはsqlc.arg(name)で名前を指定し、NULL許容のものはsqlc.narg(name)を使用。位置指定の$1はColumn1スタイルのGoフィールドを生成します。
  • 結合にはsqlc.embed(table)を使用し、アドホックなエイリアスでカラムを選択するのは避けてください。
  • スキーマ変更に耐える必要があるクエリではselect *を使用しないでください。ただし、書き込み時のreturning *は例外です。

関連スキル