From grimoire
Designs normalized relational schemas before writing DDL or ORM code. Covers entity listing, primary keys, relationships, and normalization to 3NF.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:design-data-modelThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Design a correct, normalized relational schema before writing DDL or ORM code.
Design a correct, normalized relational schema before writing DDL or ORM code.
Adopted by: Google (Spanner schema design guide), Stripe (data modeling RFC process), Amazon (schema review as part of ORR — Operational Readiness Review), Airbnb (schema review gate before any new table ships) Impact: Fixing a schema defect post-launch costs 10–100× more than fixing it at design time (Barry Boehm, "Software Engineering Economics", 1981 — ratio holds for data schema per Fowler, PEAA). Unnormalized schemas cause data anomalies (update, insertion, deletion) that corrupt production data silently. Facebook's 2012 migration off a denormalized messages schema cost 1.5 engineer-years (public post-mortem). Proper indexing at design time reduces query latency by 10–1000× compared to retrofitting indexes on large tables. Why best: Normalization to 3NF eliminates redundancy and anomalies by construction — no amount of application-layer discipline achieves the same guarantee. Alternative (denormalize for performance upfront) is premature optimization: normalized schemas can be selectively denormalized with materialized views once bottlenecks are measured.
Sources: E.F. Codd (1970), Martin Fowler (PEAA 2002), PostgreSQL indexing documentation, Stripe engineering blog
Write one line per entity: Entity — attribute1, attribute2, ...
Ask: does each attribute describe this entity, or something else? If something else, it belongs in a different entity.
Every entity must have a stable, unique, non-null identifier.
id BIGSERIAL / UUID) over natural keys unless the natural key is guaranteed stable and unique (e.g., ISO country code).For every pair of related entities, state: one-to-one, one-to-many, or many-to-many.
Apply in order:
1NF — Each column holds one atomic value. No comma-separated lists, no repeated column groups (tag1, tag2, tag3). Extract repeating groups to a child table.
2NF — Every non-key column depends on the whole PK, not part of it. If a composite PK has columns A+B and column C depends only on A, extract (A, C) to a separate table.
3NF — No transitive dependencies. If column C depends on column B which depends on PK A, extract (B, C) to its own table.
Stop at 3NF by default. Go to BCNF only if you have overlapping candidate keys causing anomalies.
Every constraint you skip becomes an application bug waiting to happen:
-- Not null where semantically required
ALTER TABLE orders ADD COLUMN user_id BIGINT NOT NULL;
-- Foreign key with explicit action
ALTER TABLE orders ADD CONSTRAINT fk_orders_user
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT;
-- Unique where business rules require it
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);
-- Check constraints for domain rules
ALTER TABLE products ADD CONSTRAINT chk_price_positive CHECK (price > 0);
Add indexes for every access pattern you know will exist at launch:
WHERE, ORDER BY, GROUP BY for high-traffic queries.-- FK index
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- Composite for common query shape: WHERE status = ? ORDER BY created_at DESC
CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
Skip indexes for: columns updated frequently, low-cardinality columns (boolean, enum with 2-3 values) on large write-heavy tables.
Check each of these before finalizing:
NUMERIC(precision, scale) — floats lose cents.TIMESTAMPTZ (UTC-stored) for anything user-facing.deleted_at column? Add partial index WHERE deleted_at IS NULL on commonly filtered queries.Translate the validated schema to DDL (SQL) or ORM models (SQLAlchemy, Django models, Prisma schema). Ensure constraints are expressed at the DB layer, not only the application layer.
Before (unnormalized):
orders: id, customer_name, customer_email, product_name, product_price, quantity
Problems: customer and product data duplicated per row; update anomaly if email changes.
After (3NF):
users: id, name, email
products: id, name, price_cents (NUMERIC)
orders: id, user_id (FK), created_at (TIMESTAMPTZ)
order_items: id, order_id (FK), product_id (FK), quantity, unit_price_cents (NUMERIC)
tags TEXT DEFAULT ''): violates 1NF; use a junction table or a DB-native array type with GIN index.0.1 + 0.2 = 0.30000000000000004; use NUMERIC.npx claudepluginhub jeffreytse/grimoire --plugin grimoireDesigns normalized (3NF) relational database schemas with indexes, constraints, naming conventions, and documentation from functional requirements. Guides entity extraction, relationships, and ERD generation.
Database schema design with normalization, relationships, constraints, and data integrity.
Designs normalized relational database schemas for PostgreSQL and MySQL from requirements, generating DDL, constraints, indexes, relationships, and migrations.