Communitygithub.com

EgoDZB/claude-skills

PostgreSQL 命名与 Schema 规范(表、列、键、约束、索引、关系、类型),适用于 .NET/EF Core、Node.js(Drizzle、Prisma、TypeORM、Knex)和 Go(sqlc)。在设计或审查 schema、编写迁移、实体配置、sqlc 查询或 SQL,以及被问及如何命名数据库对象时使用。向用户报告偏差,而不是静默更改。

claude-skills 是什么?

该技能为 PostgreSQL 数据库对象(表、列、键、约束、索引、关系、类型和 Schema)提供了一套严格且一致的命名和设计规范。它通过两种模式运行:模式 A,当用户请求 schema 且未指定名称时,直接应用这些约定;模式 B,当名称已存在或由用户指定时,技能会报告任何偏差(区分“必须修复”和“建议”),并请求批准后再进行更改。它支持多种技术栈的 ORM 设置,包括 .NET/EF Core(使用 NamingConventions)、Node.js 的 Drizzle、Prisma、TypeORM、Knex,以及 Go 的 sqlc,并为每种技术栈提供了具体的配置指导。该技能涵盖了标准列模式、键约束命名、关系映射、视图、函数、枚举类型和并发模式,对于任何需要确保 PostgreSQL schema 保持一致、可读且高效的开发者来说,都是一个有价值的资产。

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

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

PostgreSQL 约定

如何应用

有两种模式。在编写任何内容之前,请决定应用哪种模式。

模式 A — 你选择名称。 用户请求了 schema、实体或迁移,但没有指定名称。直接应用下面的约定。

模式 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 的数据、缺少外键索引、事务外的锁。
  • 推荐 = 样式和一致性。
  • 仅报告当前任务涉及的对象。如果你在其他地方注意到其他偏差,添加一行:“还有 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_by指向 users.id 的外键
标志is_active, is_verified, has_children, can_editboolean not null default false
状态statustext + check 约束、枚举或查找表
仅日期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_id外键
并发version 或系统 xmininteger / xid
自由格式数据metadata, payload, settingsjsonb

后缀具有含义:_at 表示时刻,_on/_date 表示日历日期,_id 表示引用,_count 表示计数器,_url、_path、_hash。

键和约束

对象模式示例
主键pk_<table>pk_orders
外键fk_<table>_<ref_table>_<column>fk_orders_customers_customer_id
唯一约束uq_<table>_<cols>uq_users_email
替代键 (EF Core)ak_<table>_<cols>ak_products_sku
检查约束ck_<table>_<rule>ck_orders_amount_positive
排除约束ex_<table>_<cols>ex_bookings_room_id_period
索引ix_<table>_<cols>ix_orders_customer_id_created_at
唯一索引ux_<table>_<cols>ux_users_email
部分索引ix_<table>_<cols>_<condition>ix_users_email_not_deleted
按方法索引ix_<table>_<col>_<method>ix_products_tags_gin
触发器trg_<table>_<action>trg_orders_set_updated_at
触发器函数动词 + 对象set_updated_at()
序列PostgreSQL 默认orders_id_seq
  • 外键列:<被引用表名,单数>_id,或者当角色重要时使用角色名称:author_id、sender_id、recipient_id、parent_id。
  • 外键模式包含列名,因此指向同一张表的多个外键保持唯一:fk_messages_users_sender_id、fk_messages_users_recipient_id。
  • PostgreSQL 不会自动为外键列创建索引。每个外键都需要一个索引,除非已有一个复合索引以其开头。EF Core 按惯例创建这些索引;Drizzle、Prisma、TypeORM、Knex 和 sqlc 不会。

关系

种类形状示例
一对多子表上的外键order_items.order_id → orders.id
一对一外键 + 唯一约束,或主键 = 外键user_profiles.user_id
多对多连接表 <a>_<b>,复合主键user_roles (user_id, role_id),pk_user_roles
带属性的多对多有意义的实体名称memberships,enrollments
自引用parent_id 或角色categories.parent_id,employees.manager_id

多态对(commentable_type + commentable_id)不能有外键。优先使用多个可空的特定外键列,并配合检查约束确保恰好设置了一个,或者使用单独的表。

Schema、视图、函数、类型

对象模式示例
Schema域或模块auth,billing,catalog,audit
视图v_<name>v_active_users
物化视图mv_<name>mv_daily_sales
函数 / 过程动词 + 对象get_user_balance,calculate_order_total
枚举类型<entity>_<attribute>order_status
枚举值小写 snake_casepending,paid,refunded
数据库角色<app>_<access>shop_app_rw,shop_app_ro,shop_migrator

枚举值不能在重建类型的情况下删除。对于会发生变化的集合,优先使用 text + check 约束或查找表。

类型和默认值

  • 时刻使用 timestamptz,默认值 now()。在 timestamptz 列上使用 CURRENT_TIMESTAMP AT TIME ZONE 'UTC' 是一个错误。
  • 使用 text 而不是 varchar(n),除非长度是一个业务规则;在这种情况下优先使用 check (char_length(col) <= n)。
  • 使用 jsonb,永远不要用 json。
  • 金额使用 numeric;永远不要用 real、double precision 或 money。
  • 新的标识列使用 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 更新;零受影响行表示冲突。在 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 生成

如果项目使用了 ORM 但未配置 snake_case,在首次迁移前将其报告为“必须修复”;在迁移已存在后,将其报告为“推荐”,并附带迁移成本。

Go + sqlc

sqlc 是 SQL 优先的:schema 来自迁移文件,因此上述所有规则都直接适用于它们。

  • 将 sqlc.yaml 中的 schema 指向迁移目录(支持 goose、golang-migrate、dbmate 和 atlas 格式;会忽略 down 迁移)。切勿在迁移旁边保留单独的手写 schema 文件。
  • 复数 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) 而不是选择带有临时别名列的查询。
  • 在应该能承受 schema 变化的查询中,永远不要使用 select *,除了写操作中的 returning *。

相关技能