Communitygithub.com

EgoDZB/claude-skills

PostgreSQL 命名與結構慣例(資料表、欄位、鍵、限制、索引、關聯、型別),適用於 .NET/EF Core、Node.js(Drizzle、Prisma、TypeORM、Knex)和 Go(sqlc)。在設計或審查結構、編寫遷移、實體設定、sqlc 查詢或 SQL,或被問及如何命名資料庫物件時使用。會向使用者報告偏差,而非默默修改。

claude-skills 是什麼?

這是一個專為 AI 代理(如 Claude Code、Cursor、Codex)設計的技能,旨在強制執行 PostgreSQL 資料庫物件的命名與結構慣例。它特別適用於 .NET/EF Core、Node.js(Drizzle、Prisma、TypeORM、Knex)和 Go(sqlc)技術棧。此技能的主要運作模式是:當使用者要求設計或審查結構時,它會根據明確的規則(如 snake_case、避免保留字、正確的 FK 索引)直接套用慣例(模式 A)。如果使用者已經指定了名稱,或現有程式碼違反了慣例,它會清晰報告「必須修正」和「建議」的偏差,並在變更前請求確認(模式 B),確保不會擅自修改。這能有效防止因命名不一致、錯誤的資料型別(如使用 json 而非 jsonb)或缺少索引而導致的錯誤,並協助開發團隊維持一致的資料庫設計。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/EgoDZB/claude-skills/tree/HEAD/skills/postgres-conventions

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

PostgreSQL 慣例

如何套用

有兩種模式。在開始撰寫任何內容之前,請先決定使用哪一種。

模式 A — 由你選擇名稱。 使用者要求設計結構、實體或遷移,但未指定名稱。請直接套用下方的慣例。

模式 B — 名稱已存在或已被指定。 使用者已指定名稱,或者現有程式碼、遷移或資料庫中的名稱與這些慣例不符。切勿擅自重新命名。請回報並詢問。

在進行任何變更之前,請使用使用者的語言,以下列格式回報:

命名:
必須修正
- `"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` — 資料表名稱本身就已是上下文

然後問一個問題:全部套用、只套用「必須修正」,或保持原樣。等待回答。

回報規則:

  • 必須修正 = 會導致錯誤、破壞查詢或工具:加上引號的大小寫混合識別碼、保留字、長度超過 63 位元組的名稱、timestamp(無時區)、錯誤的時間預設值、json/float/money(用於需要 jsonb/numeric 的資料)、缺少 FK 索引、在交易外部使用鎖。
  • 建議 = 樣式與一致性。
  • 僅回報目前任務所觸及的物件。如果你在意發現其它偏差,請加一行:「此外還有 N 個類似的偏差在 <位置> — 需要列出嗎?」
  • 已套用的遷移或線上資料庫中的物件:重新命名是一項破壞性變更。僅提出建議;如果獲得批准,則以新遷移實作。切勿編輯已套用的遷移。
  • 如果程式碼庫一致使用不同的慣例,則以該慣例為準。說明一次差異,然後遵循專案慣例,無需每次出現都標記。
  • 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_byFK 到 users.id
標誌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 模式包含欄位,因此同一個資料表的多個 FK 會保持唯一:fk_messages_users_sender_id、fk_messages_users_recipient_id。
  • PostgreSQL 不會為 FK 欄位自動建立索引。每個 FK 都需要一個索引,除非複合索引已以該欄位開頭。EF Core 會依慣例建立這些索引;Drizzle、Prisma、TypeORM、Knex 和 sqlc 則不會。

關聯

種類形狀範例
一對多FK 在子表格上order_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。建議使用多個可為空的 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' 是一個錯誤。
  • 使用 text 替代 varchar(n),除非長度是業務規則;若需限制長度,建議使用 check (char_length(col) <= n)。
  • 使用 jsonb,絕對不要用 json。
  • 使用 numeric 表示金額;絕對不要用 real、double precision 或 money。
  • 新的 identity 欄位使用 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:使用 uint 屬性並設定 IsRowVersion() 對應到 xmin。
  • 樂觀並行,Node 和 Go:使用 version integer not null default 0,透過 where id = $1 and version = $2 和 version = version + 1 更新;受影響行數為零表示發生衝突。在 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 僅適用於小型有限的列表。
  • 未來資料表的權限需要 alter default privileges,而不僅僅是 grant ... on all tables。

各 ORM 的 snake_case 設定

技術棧設定
EF Core + NpgsqlEFCore.NamingConventions,UseSnakeCaseNamingConvention()
Drizzlecasing: 'snake_case'
Prisma欄位使用 @map,模型使用 @@map
TypeORM來自 typeorm-naming-strategies 的 SnakeNamingStrategy
Knex / ObjectionknexSnakeCaseMappers()
Go + sqlc無;Go 名稱會自動從 snake_case 產生

如果專案使用未設定 snake_case 的 ORM,請在首次遷移前將其回報為「必須修正」;在遷移已存在後,則回報為「建議」,並說明遷移成本。

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 動詞 + 物件,並加上結果種類:
用途範例
單行-- name: GetUserByEmail :one
多行-- name: ListOrdersByCustomer :many
插入-- name: CreateOrder :one + returning *
更新-- name: UpdateOrderStatus :execrows
刪除-- name: DeleteSession :exec
大量載入-- name: CreateOrderItems :copyfrom
  • 使用 @name 或 sqlc.arg(name) 命名參數,並使用 sqlc.narg(name) 表示可為空的參數;位置參數 $1 會產生 Column1 風格的 Go 欄位。
  • 在聯結中使用 sqlc.embed(table),而非選取帶有臨時別名的欄位。
  • 在查詢中勿使用 select *,除非是寫入操作的 returning *,以確保查詢能承受結構變更。

相關技能