Database schema design for PostgreSQL/MySQL with normalization, relationships, constraints. Use for new databases, schema reviews, migrations, or encountering missing PKs/FKs, wrong data types, premature denormalization, EAV anti-pattern.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Database schema design for PostgreSQL/MySQL with normalization, relationships, constraints. Use for new databases, schema reviews, migrations, or encountering missing PKs/FKs, wrong data types, premature denormalization, EAV anti-pattern.
license
MIT
metadata
{"keywords":"database schema, schema design, database normalization, 1nf 2nf 3nf, primary key, foreign key, database relationships, one to many, many to many, data types postgresql, constraints check, audit columns, soft delete, database best practices, schema patterns, database anti-patterns, missing primary key, no foreign key, varchar max, denormalization, entity relationship, composite key, uuid vs bigserial, timestamptz"}
database-schema-design
Comprehensive database schema design patterns for PostgreSQL and MySQL with normalization, relationships, constraints, and error prevention.
Quick Start (10 Minutes)
Step 1: Choose your schema pattern from templates:
-- Always index foreign keysCREATE TABLE order_items (
order_id UUID NOT NULLREFERENCES orders(id),
product_id UUID NOT NULLREFERENCES products(id)
);
-- ✅ Required indexesCREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
Error 6: Missing Audit Columns
Symptom: Cannot track when records created/modified
Fix:
-- ❌ BadCREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(200)
);
-- ✅ GoodCREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(200) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- Auto-update trigger (PostgreSQL)CREATETRIGGER products_updated_at
BEFORE UPDATEON products
FOREACHROWEXECUTEFUNCTION update_updated_at_column();
Error 7: EAV Anti-Pattern
Symptom: Complex queries, no type safety, slow performance
Fix:
-- ❌ Bad (EAV)CREATE TABLE product_attributes (
product_id UUID,
attribute_name VARCHAR(100), -- 'color', 'size', 'price'
attribute_value TEXT -- Everything as text!
);
-- ✅ Good (Structured + JSONB)CREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL, -- Required fields as columns
color VARCHAR(50), -- Common attributes as columns
size VARCHAR(20),
attributes JSONB -- Optional/dynamic attributes
);
-- Index JSONBCREATE INDEX idx_products_attributes ON products USING GIN(attributes);
Loadreferences/error-catalog.md for all 12 errors with detailed fixes.
Recommendation: Design to 3NF, denormalize only with measured performance data.
Loadreferences/normalization-guide.md for detailed examples with before/after.
Configuration Summary
PostgreSQL Recommended Types
-- Primary Keys
id UUID PRIMARY KEYDEFAULT gen_random_uuid()
-- OR for performance-critical:
id BIGSERIAL PRIMARY KEY-- Text
name VARCHAR(200) NOT NULL
description TEXT
code CHAR(10) -- Fixed-length codes only-- Numbers
price DECIMAL(10,2) NOT NULL-- Money: NEVER use FLOAT
quantity INTNOT NULL
rating DECIMAL(3,2) -- 0.00 to 9.99-- Dates/Times
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL-- With timezone
event_date DATE
duration INTERVAL-- Boolean
is_active BOOLEANDEFAULTtrueNOT NULL-- JSON
attributes JSONB -- Binary, faster, indexable-- Enum Alternative (preferred over ENUM type)
status VARCHAR(20) NOT NULLCHECK (status IN ('draft', 'active', 'archived'))
MySQL Differences
-- MySQL doesn't have:
TIMESTAMPTZ -- Use TIMESTAMP (stored as UTC)
gen_random_uuid() -- Use UUID() function
JSONB -- Use JSON (same performance in 8.0+)-- MySQL equivalent:
id CHAR(36) PRIMARY KEYDEFAULT (UUID())
-- OR:
id BIGINT AUTO_INCREMENT PRIMARY KEY
created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMPNOT NULL
attributes JSON
Loadreferences/data-types-guide.md for comprehensive type selection guide.
When to Load References
Schema Design Process
Loadreferences/schema-design-patterns.md when:
Starting a new database design
Need pattern examples (audit columns, soft deletes, versioning)
Implementing multi-tenancy
Choosing between UUID vs BIGSERIAL
Following naming conventions
Normalization
Loadreferences/normalization-guide.md when:
Schema has data duplication
Unsure what normal form you're in
Need to normalize existing schema
Planning database structure
Relationships
Loadreferences/relationship-patterns.md when:
Defining table relationships
Implementing junction tables
Creating hierarchical structures
Setting up cascade rules
Data Types
Loadreferences/data-types-guide.md when:
Choosing column types
Migrating between PostgreSQL/MySQL
Optimizing storage
Implementing JSON fields
Constraints
Loadreferences/constraints-catalog.md when:
Adding validation rules
Implementing CHECK constraints
Setting up foreign key cascades
Creating unique constraints
Error Prevention
Loadreferences/error-catalog.md when:
Schema review needed
Troubleshooting schema issues
All 12 documented errors with fixes
Complete Setup Checklist
Before Creating Tables:
Normalized to at least 3NF
All relationships identified
Data types chosen appropriately
Cascade rules defined
Every Table Must Have:
Primary key defined
Audit columns (created_at, updated_at)
NOT NULL on required fields
Appropriate VARCHAR lengths (not MAX)
CHECK constraints for enums/ranges
Foreign Keys:
All foreign keys defined with REFERENCES
ON DELETE/UPDATE actions specified
All foreign keys indexed
Indexes:
Foreign keys indexed
Frequently queried columns indexed
Composite indexes for multi-column queries
Validation:
No circular dependencies
No EAV patterns
No polymorphic associations
Proper data types (no dates as strings)
Production Example
Before (Multiple issues):
CREATE TABLE users (
email VARCHAR(MAX), -- Issue: No primary key, VARCHAR(MAX)
password VARCHAR(MAX),
created VARCHAR(50) -- Issue: Date as string
);
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_email VARCHAR(MAX), -- Issue: No foreign key
total VARCHAR(20), -- Issue: Money as string
status VARCHAR(MAX) -- Issue: No validation
);
After (Production-ready):
CREATE TABLE users (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUENOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
CREATE TABLE orders (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
user_id UUID NOT NULLREFERENCES users(id) ONDELETE CASCADE,
total DECIMAL(10,2) NOT NULLCHECK (total >=0),
status VARCHAR(20) NOT NULLDEFAULT'pending'CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'canceled')),
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
Result: ✅ All constraints enforced, proper types, indexed, auditable
Known Issues Prevention
All 12 documented errors prevented:
✅ Missing primary key → UUID/BIGSERIAL required
✅ No foreign key constraints → REFERENCES required
✅ VARCHAR(MAX) everywhere → Appropriate lengths
✅ Denormalization without justification → 3NF minimum
✅ Missing NOT NULL constraints → Required fields marked
✅ No indexes on foreign keys → All FKs indexed
✅ Wrong data types → Proper type selection
✅ Missing CHECK constraints → Validation rules
✅ No audit columns → created_at/updated_at required
✅ Circular dependencies → Dependency analysis
✅ Missing ON DELETE/UPDATE cascades → Cascade rules
✅ EAV anti-pattern → Structured schema + JSONB
See: references/error-catalog.md for detailed fixes