Schema design is a product decision
Your database schema isn't just a technical artifact — it shapes query performance, application complexity, and how easily your system evolves. A schema optimized for writes (OLTP) looks very different from one optimized for reads (OLAP). Understanding these trade-offs upfront saves months of painful migrations later.
Normalization fundamentals
Normal forms reduce data redundancy and prevent update anomalies:
- 1NF: Atomic values only — no arrays or nested objects in cells.
- 2NF: Every non-key column depends on the entire primary key (eliminates partial dependencies).
- 3NF: No transitive dependencies — non-key columns depend only on the primary key, not on other non-key columns.
For OLTP systems, 3NF is the sweet spot. Over-normalizing (BCNF, 4NF) adds JOIN complexity for diminishing returns.
When to denormalize
Analytics workloads benefit from denormalization — pre-computed JOINs, aggregation columns, and wide tables that reduce query complexity. Star schema and snowflake schema are the two dominant patterns for data warehouses.
Denormalize when:
- Read performance matters more than write consistency
- Queries always join the same tables
- Your warehouse engine (Snowflake, BigQuery) handles wide tables efficiently
- ETL/ELT can handle the transformation overhead
Build your architecture diagram now
Datadef generates professional diagrams with AI — 2,000+ cloud icons, column-level data lineage, and an MCP server your coding agent can drive.
Indexing strategies
B-tree indexes
Default index type in PostgreSQL, MySQL, and SQL Server. Optimal for equality and range queries. Create indexes on foreign keys, frequently-filtered columns, and ORDER BY targets.
Composite indexes
Multi-column indexes follow the leftmost-prefix rule. Index (country, city, zipcode) supports queries filtering by country alone, country+city, or all three — but not city alone.
Partial and expression indexes
PostgreSQL's partial indexes (WHERE active = true) and expression indexes (LOWER(email)) dramatically reduce index size and improve performance for filtered or transformed queries.
Schema patterns for common scenarios
- Multi-tenancy: Shared tables with tenant_id (simple, scalable) vs schema-per-tenant (isolation) vs database-per-tenant (maximum isolation, ops overhead).
- Soft deletes: Add deleted_at timestamp instead of hard DELETE. Filter with WHERE deleted_at IS NULL. Partial indexes keep queries fast.
- Audit trails: Separate audit table with trigger-based change logging, or use event sourcing patterns.
- Polymorphic associations: Avoid record_type + record_id anti-pattern. Use separate join tables or table inheritance instead.
Visualizing your schema
Entity-relationship diagrams (ERDs) should be living documents, not one-time design artifacts. Datadef lets you diagram your database schema with AI, connect tables with join relationships, and track column-level lineage downstream into your analytics layer.