From sql-development
Use this skill whenever the user is doing anything with a relational database. Triggers include: designing or modifying schemas, creating or altering tables and columns, defining relationships, writing or reviewing migrations, writing SQL queries (SELECT, INSERT, UPDATE, DELETE, DDL), optimizing queries, adding indexes, reviewing ERDs, seeding data, or working with ORMs against a relational backend (Hibernate, JPA, Prisma, Drizzle, SQLAlchemy, ActiveRecord, TypeORM, jOOQ, etc.). Applies to relational systems: Postgres, MySQL, MariaDB, SQLite, SQL Server, Oracle. If the task touches a relational database in any way, use this skill.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sql-development:database-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill defines the conventions to follow when designing relational database schemas. Apply these rules whenever creating or modifying tables, columns, or relationships. When a rule conflicts with something the user has already established in their existing schema, flag the conflict and ask before deviating.
This skill defines the conventions to follow when designing relational database schemas. Apply these rules whenever creating or modifying tables, columns, or relationships. When a rule conflicts with something the user has already established in their existing schema, flag the conflict and ask before deviating.
When something is not fully specified — any decision is open (column type, length, nullability, naming, indexes, relationships, on-delete behavior, default values, etc.) — invoke the ask-user-questions skill and ASK. Never make the decision yourself. Do not infer "reasonable defaults", do not pick "the obvious choice", do not silently fill gaps. If the user did not state it, ask.
When asking, always include the best-practice option (with the reasoning) as one of the choices — typically the first option, marked as recommended. Don't present neutral menus that hide which choice is industry-standard or safer; explain why the recommended option is the default so the user can make an informed call (or override knowingly).
Rules are written dialect-agnostically. Where the syntax or behavior differs between Postgres, MySQL, SQL Server, Oracle, and SQLite, the rule includes a per-dialect note. When a single name is needed for a feature, Postgres terminology is used (e.g. timestamp with time zone).
If the project's dialect is unclear, ASK before producing DDL.
select, create table, not null, primary key, default, references, etc.), built-in types (uuid, timestamp with time zone, varchar, text), and function names are written in lowercase.snake_case. Tables, columns, constraints, indexes — all snake_case. No camelCase, no PascalCase. Postgres folds unquoted identifiers to lowercase anyway, and snake_case removes the need to ever quote them.git blame. If a logical change requires multiple statements, that's multiple Flyway files in the right V order.V<number>__<description>.sql — e.g. V0042__add_audit_columns_to_user.sql. Versioned migrations only; if the project also uses repeatable (R__) or undo (U__) migrations, follow project convention but ASK on first use.user, order, invoice_line_item — never users, orders, invoice_line_items.idx_ — indexpk_ — primary keyfk_ — foreign keyuq_ — unique constraintck_ — check constraintdf_ — default constraint (SQL Server only — Postgres and MySQL don't name default constraints. Skip on those dialects.)Words reserved in at least one major dialect — avoid as table or column names. Pick a non-reserved synonym instead.
| Word | Reserved in |
|---|---|
user | Postgres, MySQL, SQL Server, Oracle |
order | All |
group | All |
select, from, where, join | All |
desc | All — and easy to grab as a short form of description |
type | Postgres |
table, column | All |
default, primary, references | All |
lock, read, write | MySQL, Oracle |
If a domain name collides (you really want a user table), use a non-reserved alternative: account, app_user, member. Don't quote.
id, in this exact order: id, created_at, created_by, updated_at, updated_by, then domain columns. Pair each timestamp with its actor: created_at is followed by created_by, and updated_at is followed by updated_by. Never interleave domain columns between audit columns.id, created_at, created_by, updated_at, updated_by — in that order. Apply them as-is without asking.id, created_at, created_by, updated_at, updated_by, deleted_at, deleted_by, <domain columns>. Soft delete is opt-in — see the soft-delete rule below.id) typeASK the user which id type to use when designing a new table or adding id to an existing one. Don't pick silently.
uuid column. UUID v7 generation: pg_uuidv7 extension, or generate app-side.binary(16) storage with an app-side UUID v7 generator. Avoid char(36) for the index size cost.uniqueidentifier; v7 generated app-side (newid() is v4).text storing the canonical 36-char form, or blob for the 16-byte form.bigserial / IDENTITY (auto-increment integer — compact, sequential, leaks insertion volume), uuid v4 (random — globally unique but worse btree index locality than v7).created_at, updated_at)timestamp with time zone (a.k.a. timestamptz). Use the long form per the SQL style rule, never the shorthand.timestamp with time zone.datetimeoffset.timestamp (always stored as UTC; no separate "with timezone" syntax). Configure session/server timezone explicitly. datetime is naive — don't use it for these columns.text storing ISO 8601 with offset, or integer Unix epoch. Document the convention in a column comment.default current_timestamp (or equivalent) for created_at. Use a trigger or the dialect's built-in mechanism (on update current_timestamp in MySQL) for updated_at. The DB owns these values.@CreationTimestamp / @CreatedDate / @default(now()) are fine. If raw SQL writes happen anywhere (Flyway data migrations, scheduled jobs, direct INSERTs), the DB must own the timestamp — otherwise values drift between code paths and the bug is invisible until you order by created_at.created_by, updated_by)ASK the user how actors are referenced — there's no single right answer:
user table (bigint or uuid references user(id)) — strongest integrity; system jobs need a sentinel system user row.actor_type + actor_id) — flexible; lets non-human actors (cron, integrations) be first-class.The decision usually depends on the auth model and whether non-human actors need to write. Ask when designing the first table that uses these; the answer applies project-wide.
deleted_at, deleted_by)deleted_at (timestamp with time zone), deleted_by (same type as created_by/updated_by). Both default null.where deleted_at is null. Forgotten filters cause silent data leaks. Encode the filter in a view, an ORM scope, or a row-level security policy — don't trust individual query authors to remember.version column)Default: no version column. Add only on entities with real concurrent-edit potential — user-edited orders, profiles, configs that multiple admins touch. Append-only or single-writer entities don't need it.
ASK before adding. The skill prompts on entities where contention is plausible.
When added: an integer column, default 0, incremented on every update. The application or ORM handles the bump (@Version in Hibernate / JPA, equivalent in Spring Data, etc.). Update statements check the previous version:
update foo set ..., version = version + 1 where id = ? and version = ?
Zero rows affected → optimistic-lock failure; the application retries or surfaces the conflict.
delete or update of the parent row scans the child table.(tenant_id, created_at) serves where tenant_id = ?, where tenant_id = ? and created_at > ?, and where tenant_id = ? order by created_at — but not where created_at > ? alone.archived = false, an index where archived = false is much smaller and faster than a full one. MySQL has no partial indexes; use a generated column + index as a workaround.create type role as enum ('admin', 'member'), then role role. Adding a value is a migration (alter type role add value 'guest'); that's a feature, not a bug.enum('admin', 'member') inline on the column.varchar + check constraint, or a lookup table with a foreign key. Document the "enum" intent in the column comment.text by default, but don't artificially cap fields that genuinely grow either.
varchar(n) with a length that reflects the domain. A name column should be varchar(100..255) — pick a number that fits the domain, not the dialect's max.text.text and varchar perform identically and use the same storage; varchar(n) is purely a length check. Use text when there's no real upper bound, varchar(n) when the bound is meaningful.varchar(n) and text differ in storage and indexing. Pick deliberately.jsonb (always — json keeps text formatting and is slower to query).json (since 5.7).nvarchar(max) with the json validity check; use the JSON functions for querying.text plus the json1 extension functions.8330 Pfäffikon ZH, Florastrasse 123 should not live in a single address field — split it into postal_code, city, street, house_number. Same for full names (first_name / last_name), coordinates (latitude / longitude), and similar composites.When the migration runs against a live production database, schema changes can lock tables, break running queries, or block writes. The rules below cover the common gotchas; for anything riskier, ASK.
not null column to a large table — never in one step. Three migrations: (1) add the column nullable with a default, (2) backfill in batches, (3) set not null. The naive alter table ... add column foo not null default ... rewrites every row in many DBs and locks the table for the duration.drop column migration.create index concurrently, no transaction.algorithm=inplace, lock=none where supported.with (online = on) on Enterprise edition.create index takes a write lock and can be many minutes on a hot table.add constraint ... not valid (skips the validation scan, locks briefly), then alter table ... validate constraint ... (no lock) afterwards. Splits the lock-heavy validation from the cutover.< 1M rows) and a brief outage is acceptable, the strict online-safety rules don't all apply — but ASK to confirm before bypassing them.Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.
npx claudepluginhub pgatzka/pgatzka-skills