database-schema
Skill1 starsChanged 38 days agoMITView on GitHub
Designs normalized, constrained, migration-friendly relational schemas - entity modeling, key and type selection, indexes derived from real query patterns, and safe forward/rollback migrations. Use when someone asks "design a schema for X", "should I normalize or denormalize this", "what should my primary key be", "how do I add this column without downtime", or "why is my unique constraint broken with soft deletes". Do NOT use for tuning a slow query on an existing schema - use sql-query-optimizer instead; for picking indexes from a live workload, use index-advisor; for sizing database connection pools, use connection-pool-tuner; for range/hash partitioning decisions, use partition-planner.
5 other projects use a version of this.
Adopted by
Most recently taken up by
- leookun/cursor-byoktheir own version, first seen Oct 8, 2026
- alinaqi/claude-bootstraptheir own version, first seen Oct 8, 2026
- alinaqi/maggytheir own version, first seen Jun 12, 2026
- hallucinaut/skillstheir own version, first seen Jun 10, 2026
- PengEjeen/claude_skillstheir own version, first seen May 22, 2026
Other versions5 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 5
- leookun/cursor-byok★ 3.2ksame name
- alinaqi/maggy★ 711same name
- alinaqi/claude-bootstrap★ 708same name
- hallucinaut/skills★ 0same name
- PengEjeen/claude_skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository6 other files it gives its agents.
- api-designSkill
- graphql-schemaSkill
- nextjs-app-routerSkill
- stripe-integrationSkill
- supabase-expertSkill
- web-performanceSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. 0 are common, 12 are distinctive, and 4 are very specific to this project.
0
Common
Most of the 5 edited versions we read keep these.
12
Distinctive
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 14
Follow the steps in order.
- Distinctive: line 43
Use the narrowest correct type.
- Very specific: line 47
Add NOT NULL by default, foreign keys with an explicit ON DELETE behavior (RESTRICT unless cascade is a designed feature), and CHECK …
- Very specific: line 55
Every index taxes every write.
- Distinctive: line 63
Never rename-and-reuse a column in the same deploy as the code reading it.
- Distinctive: line 102
Do not denormalize preemptively "for performance" - you inherit a sync bug without evidence of a speed problem.
- Distinctive: line 103
Do not index every column "to be safe".
- Distinctive: line 104
Do not use UUIDv4 as a primary key on high-insert tables.
- Distinctive: line 105
Do not store money in floats or timestamps without timezone - both corrupt silently.
- Very specific: line 106
Do not run a plain CREATE INDEX or an immediate SET NOT NULL on a large production table.
- Very specific: line 107
Do not forget deleted_at in unique constraints when soft-deleting - the second "delete and recreate" fails in production.
- Distinctive: line 113
Every index maps to a named query from Step 1.
- Distinctive: line 114
Every table has a primary key, and every foreign key has an explicit ON DELETE behavior.
- Distinctive: line 115
No float money, no naive timestamps, no free-text enums.
- Distinctive: line 116
Every denormalized copy names its sync mechanism and owner.
- Distinctive: line 117
Every migration in the plan is reversible and lock-safe on the stated table sizes.
“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Database Schema Designer
A schema is the one part of a system you cannot refactor with a find-and-replace: every shortcut taken at design time becomes a locked-table migration on a hot production database later. This skill produces schemas that stay correct and fast as data grows - invalid states unrepresentable, indexes matched to real queries, and every change shippable without downtime.
Operating procedure
Follow the steps in order. Constraints come before indexes because constraints define correctness and indexes only define speed; migrations come last because they depend on both.
Step 1: Gather inputs
Collect these before writing any DDL. If a number is a guess, label it a guess and proceed.
- Entities and relationships - nouns of the domain and their cardinality (1:1, 1:N, M:N).
- The top 5-10 queries by expected frequency, with their filter and sort columns. Indexes come from this list, not from intuition.
- Expected row counts at 1 year and 3 years per table, and read:write ratio per hot table. Default assumption if unknown: 10:1 read-heavy.
- Multi-tenancy - single tenant, or
tenant_idon every row? - Deletion semantics - hard delete, soft delete, or audit-retained?
Step 2: Model, normalize, then denormalize only on evidence
Normalize to third normal form (3NF) by default: every non-key column depends on the key, the whole key, and nothing but the key. Denormalize a specific read path only when ALL of these hold:
- The path is measured hot (p95 latency above target, or it dominates query volume), not predicted hot.
- The normalized query joins 3+ tables or aggregates on every read.
- The duplicated data has a read:write ratio of at least 10:1 - copies of frequently-written data rot.
- You name the sync mechanism (trigger, transactional write-through in the app, or materialized view with a refresh cadence) in the same design doc as the copy. A denormalized column with no owner is a data-corruption bug on a timer.
For M:N relationships, use a join table with a composite primary key on both foreign keys.
Step 3: Choose keys and types
- Primary keys:
BIGINT GENERATED ALWAYS AS IDENTITY, or UUIDv7 when IDs are generated client-side or must not be guessable. Never UUIDv4 as a clustered/primary key - random inserts fragment the index and wreck insert throughput on large tables. - Time:
TIMESTAMPTZ, never naive local time. - Money:
NUMERIC(19,4)or integer minor units (total_cents BIGINT), never float. - Fixed value sets: a
CHECK (col IN (...))constraint, a Postgres enum, or a lookup table. Lookup table when values change at runtime; CHECK/enum when they change with a deploy. - Use the narrowest correct type; width costs cache and index size on every row forever.
Step 4: Make invalid states unrepresentable
Add NOT NULL by default, foreign keys with an explicit ON DELETE behavior (RESTRICT unless cascade is a designed feature), and CHECK constraints for domain rules (total_cents >= 0). Every constraint you skip becomes application code that at least one code path forgets.
Step 5: Index from the query list
- Index every foreign key you join or filter on.
- Composite index column order: equality-filtered columns first, then range/sort columns.
(user_id, created_at DESC)serves "this user's recent orders"; the reverse order does not. - Covering index (
INCLUDEselected columns) when a top query can be served from the index alone. - Partial index for skewed predicates:
WHERE status = 'pending'when pending is under ~5% of rows but queried constantly. - Every index taxes every write. After launch, drop unused indexes - check
pg_stat_user_indexesfor zero-scan indexes. - Multi-tenant:
tenant_idleads every index prefix and appears in every unique key.
Step 6: Write migrations that ship without downtime
- One logical change per migration, always with a rollback.
- Adding a NOT NULL column to a large table is three steps: add nullable, backfill in batches of 1,000-10,000 rows (larger batches hold locks and bloat WAL), then add the constraint (
NOT VALIDthenVALIDATE CONSTRAINTon Postgres to avoid a full-table lock). CREATE INDEX CONCURRENTLY- a plain CREATE INDEX blocks writes for the whole build.- Never rename-and-reuse a column in the same deploy as the code reading it. Split into two deploys: add new + dual-write, then drop old.
- Soft deletes:
deleted_at TIMESTAMPTZ, and every unique constraint becomes a partial unique indexWHERE deleted_at IS NULL- otherwise a deleted row blocks re-creation forever.
Worked contrast: the schema and the migration it costs you
Bad - three common shortcuts:
CREATE TABLE orders (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY, -- UUIDv4: fragments the index
user_email TEXT, -- denormalized copy, no sync owner
status TEXT, -- free text: 'Paid', 'paid', 'PAID '
total FLOAT -- float money: rounding drift
);Migration bill this schema generates within a year: a data-cleaning pass to canonicalize every status typo before a CHECK constraint can be validated; a backfill joining users to repair stale user_email copies; a FLOAT -> NUMERIC type change that rewrites the whole table under lock; and insert throughput decay from UUIDv4 fragmentation that no migration fixes without a new PK.
Good - the same table designed forward:
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
status TEXT NOT NULL CHECK (status IN ('pending','paid','shipped')),
total_cents BIGINT NOT NULL CHECK (total_cents >= 0),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_orders_user_created ON orders (user_id, created_at DESC);Future migrations against this table are additive: new columns arrive nullable-then-backfilled, new statuses extend the CHECK, and nothing requires a cleanup pass first.
Deliverable
Produce a schema design containing: the DDL for every table with constraints inline; the index list annotated with the specific query each index serves; the denormalization decisions (if any) each naming its sync mechanism; and the migration plan for getting from the current state to the target, ordered and reversible.
Do NOT
- Do not denormalize preemptively "for performance" - you inherit a sync bug without evidence of a speed problem.
- Do not index every column "to be safe"; each index slows every write and most never get scanned.
- Do not use UUIDv4 as a primary key on high-insert tables; use BIGINT identity or UUIDv7.
- Do not store money in floats or timestamps without timezone - both corrupt silently.
- Do not run a plain
CREATE INDEXor an immediateSET NOT NULLon a large production table; both take locks that stall writes. - Do not forget
deleted_atin unique constraints when soft-deleting - the second "delete and recreate" fails in production.
Quality bar
A finished design passes only if:
- Every index maps to a named query from Step 1; zero speculative indexes.
- Every table has a primary key, and every foreign key has an explicit ON DELETE behavior.
- No float money, no naive timestamps, no free-text enums.
- Every denormalized copy names its sync mechanism and owner.
- Every migration in the plan is reversible and lock-safe on the stated table sizes.
Escalation
For query plans that stay slow on a sound schema, hand off to explain-plan-reader or sql-query-optimizer. When a table's growth projection crosses ~100M rows or needs time-based pruning, bring in partition-planner before it gets there. Gate risky production migrations through migration-safety-checker.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

