Design and optimize database schemas for SQL and NoSQL databases. Use when creating new databases, designing tables, defining relationships, indexing strategies, or database migrations. Handles PostgreSQL, MySQL, MongoDB, normalization, and performance optimization.
Design and optimize database schemas for SQL and NoSQL databases. Use when creating new databases, designing tables, defining relationships, indexing strategies, or database migrations. Handles PostgreSQL, MySQL, MongoDB, normalization, and performance optimization.
Transaction Requirements: Whether ACID is required (default: true)
Sharding/Partitioning: Whether large data distribution is needed (default: false)
Input Example
Design a database for an e-commerce platform:
- DB: PostgreSQL
- Entities: User, Product, Order, Review
- Relationships:
- A User can have multiple Orders
- An Order contains multiple Products (N:M)
- A Review is linked to a User and a Product
- Expected data: 100,000 users, 10,000 products
- Read-heavy (frequent product lookups)
Instructions
Specifies the step-by-step task sequence to follow precisely.
Step 1: Define Entities and Attributes
Identify core data objects and their attributes.
Tasks:
Extract nouns from business requirements → entities
List each entity's attributes (columns)
Determine data types (VARCHAR, INTEGER, TIMESTAMP, JSON, etc.)
Designate Primary Keys (UUID vs Auto-increment ID)
# Database Schema## Entity Relationship Diagram
\`\`\`mermaid
erDiagram
Users ||--o{ Orders : places
Orders ||--|{ OrderItems : contains
Products ||--o{ OrderItems : "ordered in"
Users {
uuid id PK
string email UK
string username UK
}
Products {
uuid id PK
string name
decimal price
}
\`\`\`
## Table Descriptions### users-**Purpose**: Store user account information
-**Indexes**: email, username
-**Estimated rows**: 100,000
### products-**Purpose**: Product catalog
-**Indexes**: category_id, price, name
- **Estimated rows**: 10,000
Constraints
Specifies mandatory rules and prohibited actions.
Mandatory Rules (MUST)
Primary Key Required: Define a Primary Key on every table
Unique record identification
Ensures referential integrity
Explicit Foreign Keys: Tables with relationships must define Foreign Keys
Specify ON DELETE CASCADE/SET NULL options
Prevent orphan records
Use NOT NULL Appropriately: Required columns must be NOT NULL
Clearly specify nullable vs. non-nullable
Providing defaults is recommended
Prohibited Actions (MUST NOT)
Avoid EAV Pattern Abuse: Use the Entity-Attribute-Value pattern only in special cases
Query complexity increases dramatically
Performance degradation
Excessive Denormalization: Be careful when denormalizing for performance
Data consistency issues
Risk of update anomalies
No Plaintext Storage of Sensitive Data: Never store passwords, card numbers, etc. in plaintext
Hashing/encryption is mandatory
Legal liability issues
Security Rules
Principle of Least Privilege: Grant only the necessary permissions to application DB accounts
SQL Injection Prevention: Use Prepared Statements / Parameterized Queries
Encrypt Sensitive Columns: Consider encrypting personally identifiable information at rest
Examples
Demonstrates how to apply the skill through real-world use cases.
Example 1: Blog Platform Schema
Situation: Database design for a Medium-style blog platform
User Request:
Design a PostgreSQL schema for a blog platform:
- Users can write multiple posts
- Posts can have multiple tags (N:M)
- Users can like and bookmark posts
- Comment feature (with nested replies)
Final Result:
-- UsersCREATE TABLE users (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUENOT NULL,
username VARCHAR(50) UNIQUENOT NULL,
bio TEXT,
avatar_url VARCHAR(500),
created_at TIMESTAMPDEFAULT NOW()
);
-- PostsCREATE TABLE posts (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
author_id UUID NOT NULLREFERENCES users(id) ONDELETE CASCADE,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUENOT NULL,
content TEXT NOT NULL,
published_at TIMESTAMP,
created_at TIMESTAMPDEFAULT NOW(),
updated_at TIMESTAMPDEFAULT NOW()
);
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_published ON posts(published_at);
CREATE INDEX idx_posts_slug ON posts(slug);
-- TagsCREATE TABLE tags (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
name VARCHAR(50) UNIQUENOT NULL,
slug VARCHAR(50) UNIQUENOT NULL
);
-- Post-Tag relationship (N:M)CREATE TABLE post_tags (
post_id UUID REFERENCES posts(id) ONDELETE CASCADE,
tag_id UUID REFERENCES tags(id) ONDELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
CREATE INDEX idx_post_tags_post ON post_tags(post_id);
CREATE INDEX idx_post_tags_tag ON post_tags(tag_id);
-- LikesCREATE TABLE post_likes (
user_id UUID REFERENCES users(id) ONDELETE CASCADE,
post_id UUID REFERENCES posts(id) ONDELETE CASCADE,
created_at TIMESTAMPDEFAULT NOW(),
PRIMARY KEY (user_id, post_id)
);
-- BookmarksCREATE TABLE post_bookmarks (
user_id UUID REFERENCES users(id) ONDELETE CASCADE,
post_id UUID REFERENCES posts(id) ONDELETE CASCADE,
created_at TIMESTAMPDEFAULT NOW(),
PRIMARY KEY (user_id, post_id)
);
-- Comments (self-referencing for nested comments)CREATE TABLE comments (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
post_id UUID NOT NULLREFERENCES posts(id) ONDELETE CASCADE,
author_id UUID NOT NULLREFERENCES users(id) ONDELETE CASCADE,
parent_comment_id UUID REFERENCES comments(id) ONDELETE CASCADE,
content TEXT NOT NULL,
created_at TIMESTAMPDEFAULT NOW(),
updated_at TIMESTAMPDEFAULT NOW()
);
CREATE INDEX idx_comments_post ON comments(post_id);
CREATE INDEX idx_comments_author ON comments(author_id);
CREATE INDEX idx_comments_parent ON comments(parent_comment_id);
Example 2: MongoDB Schema (NoSQL)
Situation: MongoDB schema for a real-time chat app
User Request:
Design a MongoDB schema for a real-time chat app.
Reads are very frequent, and message history needs to be retrieved quickly.
Denormalization for read optimization (embedding last_message)
Indexes on frequently accessed fields
Using array fields (participants, read_by)
Best practices
Quality Improvement
Naming Convention Consistency: Use snake_case for table/column names
users, post_tags, created_at
Be consistent with plurals/singulars (tables plural, columns singular, etc.)
Consider Soft Delete: Use logical deletion instead of physical deletion for important data
deleted_at TIMESTAMP (NULL = active, NOT NULL = deleted)
Allows recovery of accidentally deleted data
Audit trail
Timestamps Required: Include created_at and updated_at in most tables
Data tracking and debugging
Time-series analysis
Efficiency Improvements
Partial Indexes: Minimize index size with conditional indexes
CREATE INDEX idx_posts_published ON posts(published_at) WHERE published_at ISNOT NULL;
Materialized Views: Cache complex aggregate queries as Materialized Views
Partitioning: Partition large tables by date/range
Common Issues
Issue 1: N+1 Query Problem
Symptom: Multiple DB calls when a single query would suffice
Cause: Individual lookups in a loop without JOINs
Solution:
-- ❌ Bad example: N+1 queriesSELECT*FROM posts; -- 1 time-- for each postSELECT*FROM users WHERE id = ?; -- N times-- ✅ Good example: 1 querySELECT posts.*, users.username, users.avatar_url
FROM posts
JOIN users ON posts.author_id = users.id;
Issue 2: Slow JOINs Due to Unindexed Foreign Keys
Symptom: JOIN queries are very slow
Cause: Missing index on Foreign Key column
Solution:
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
Issue 3: UUID vs Auto-increment Performance
Symptom: Insert performance degradation when using UUID Primary Keys
Cause: UUIDs are random, causing index fragmentation
Solution:
PostgreSQL: Use uuid_generate_v7() (time-ordered UUID)