| name | relational-data-modeling |
| description | Corrects the wrong defaults a model has when designing a relational schema — the DDL decisions an experienced engineer makes differently. Use when creating or reviewing tables, migrations, ER models, or ORM schema definitions. Covers identity and keys (surrogate vs natural, identity vs serial, uuidv7, composite keys that make cross-tenant references impossible), relationships (polymorphic foreign keys the database cannot enforce, referential actions, unindexed FK columns, disjoint subtypes), invariants the engine can prove instead of application code (EXCLUDE, partial unique indexes, CHECK limits, deferrable cycles, NOT VALID), types (timestamptz, exact money, range types, enum vs lookup table), derived and encoded data (generated columns, JSONB as an escape hatch), and time (events vs in-place updates, soft-delete flags that silently disable constraints, temporal keys). NOT for query tuning, index selection for read paths, or connection pooling. |
Relational Data Modeling
The decisions a relational schema forces, and how to settle them so the database
enforces what it can and the application is left with only what it must. Every
rule names the wrong default it corrects; there is no rule for what a capable
model already gets right.
Examples are PostgreSQL 18, and every DDL statement in this skill was
executed against 18.4 — including the failure cases, to confirm the constraints
reject what they claim to reject. Roughly a third of the rules depend on
mechanisms MySQL and SQLite do not have (EXCLUDE, partial unique indexes,
range types, WITHOUT OVERLAPS, deferrable constraints, NOT VALID); those
rules say so. The judgment rules transfer to any relational engine.
When to Apply
Use this skill when:
- Writing or reviewing
CREATE TABLE / ALTER TABLE, a migration, or an ORM
schema definition (Prisma, Drizzle, Django models, ActiveRecord, Ecto, SQLAlchemy)
- Designing an entity-relationship model, or naming what a row is — the point
where key choices become expensive to reverse
- The user says "should this be one table or two", "how do I model many-to-many",
"the schema allows bad data", "we need history", "we're adding multi-tenancy",
or "can the database enforce this"
- A bug turns out to be a schema that permitted the bad state — duplicates that
a unique constraint should have caught, orphans a foreign key should have
blocked, overlapping bookings, two rows flagged as default
- Adding constraints to a table that already has rows and traffic
- Reviewing a schema generated by an ORM or a scaffolding tool, which is where
polymorphic associations, reflexive surrogate keys, and blanket soft-delete
flags arrive from
This skill is NOT for:
- Query tuning, execution plans, or choosing indexes for a read path — this
covers only the indexes that constraints and foreign keys require
- Connection pooling, replication, or operational tuning
- Non-relational stores, where the trade-offs it argues from do not hold
Rule Categories
| # | Category | Prefix | Covers |
|---|
| 1 | Identity and Keys | key- | What a row is; the choice every foreign key depends on |
| 2 | Relationships and Cardinality | rel- | Keeping references declarable to the database, not just intended |
| 3 | Constraints as the Model | cons- | Which mechanism can actually hold which invariant |
| 4 | Types and Domains | type- | The cheapest constraint available, chosen for meaning not habit |
| 5 | Derived and Encoded Data | norm- | What every deliberate copy costs, and what the database can't see inside |
| 6 | Time, History and Lifecycle | time- | What happens to a row when the world changes |
Quick Reference
1. Identity and Keys
2. Relationships and Cardinality
3. Constraints as the Model
4. Types and Domains
5. Derived and Encoded Data
6. Time, History and Lifecycle
How to Use
Read a reference file when its decision comes up — the quick reference above is
enough to route. Each rule states the wrong default it corrects and why, then
gives a canonical example.
Two rules of thumb tie the categories together and are worth applying even
outside a specific rule:
- Before adding a column, name what makes two rows the same row. That
sentence is a constraint you owe the table.
- Before writing a validation in application code, ask which constraint could
hold it instead. If the answer is "none", that is worth knowing explicitly
— see
cons-check-is-single-row for
the map of invariant shapes to mechanisms.
Reference Files