Learning Objectives
- Understand the fundamentals of relational database design
- Learn about database normalization (1NF, 2NF, 3NF, BCNF)
- Master entity-relationship modeling concepts
- Practice designing efficient database schemas
- Understand data types and their appropriate usage
- Learn about indexing strategies and performance optimization
Table of Contents
- Introduction to Database Design
- Entity-Relationship Modeling
- Database Normalization
- Data Types and Storage
- Indexing Strategies
- Design Patterns and Best Practices
- Performance Considerations
- Real-World Design Examples
Introduction to Database Design
What is Database Design?
Database design is the process of creating a detailed data model of a database. It involves: - Analyzing requirements and understanding business rules - Identifying entities and their relationships - Designing tables with appropriate columns and data types - Establishing relationships between tables - Optimizing for performance and maintainability
Key Principles of Good Database Design
1. Data Integrity
-- Example: Ensuring data consistency
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(10,2) NOT NULL CHECK (total_amount > 0),
status ENUM('pending', 'processing', 'shipped', 'delivered') DEFAULT 'pending',
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
2. Elimination of Redundancy
-- BAD: Redundant data storage
CREATE TABLE bad_orders (
order_id INT,
customer_name VARCHAR(100), -- Redundant if stored in customers table
customer_email VARCHAR(100), -- Redundant if stored in customers table
product_name VARCHAR(100), -- Redundant if stored in products table
product_price DECIMAL(10,2) -- Redundant if stored in products table
);
-- GOOD: Normalized design
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
3. Flexibility and Scalability
-- Flexible design that can accommodate future changes
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- JSON field for flexible user attributes
metadata JSON
);
Entity-Relationship Modeling
Understanding Entities and Relationships
Entities
Entities are objects or concepts that have independent existence and can be uniquely identified.
Example Entities: - Student - Course - Professor - Department - Enrollment
Attributes
Attributes are properties or characteristics of entities.
Types of Attributes: - Simple vs Composite: Simple (age) vs Composite (address) - Single-valued vs Multi-valued: Single (SSN) vs Multi-valued (phone numbers) - Stored vs Derived: Stored (birth_date) vs Derived (age) - Key vs Non-key: Key (student_id) vs Non-key (name)
Relationships
Relationships describe how entities are connected.
Relationship Types:
1. One-to-One (1:1)
-- Example: User and UserProfile
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL
);
CREATE TABLE user_profiles (
profile_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT UNIQUE NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
2. One-to-Many (1:M)
-- Example: Department and Employees
CREATE TABLE departments (
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(100) NOT NULL
);
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);
3. Many-to-Many (M:N)
-- Example: Students and Courses
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL
);
CREATE TABLE courses (
course_id INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(100) NOT NULL,
credits INT NOT NULL
);
-- Junction table for many-to-many relationship
CREATE TABLE enrollments (
student_id INT,
course_id INT,
enrollment_date DATE DEFAULT (CURRENT_DATE),
grade DECIMAL(5,2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id)
);
ER Diagram Notation
Entity: [Entity Name]
|-- Attribute (Key)
|-- Attribute
|-- Attribute (Derived)
Relationship: Entity1 --< Relationship >-- Entity2
Example ER Diagram:
[Student] [Course]
|-- student_id (PK) |-- course_id (PK)
|-- first_name |-- course_name
|-- last_name |-- credits
|-- email |-- dept_id (FK)
|-- gpa |
|
[Enrollment] (Junction Table)
|-- student_id (FK)
|-- course_id (FK)
|-- enrollment_date
|-- grade
Database Normalization
What is Normalization?
Normalization is the process of organizing data in a database to: - Eliminate redundancy - Reduce data anomalies - Improve data integrity - Optimize storage space
First Normal Form (1NF)
Rules: 1. Each table cell must contain only atomic (indivisible) values 2. Each record must be unique 3. No repeating groups or arrays
Example: Violating 1NF
-- BAD: Violates 1NF - repeating groups
CREATE TABLE bad_students (
student_id INT PRIMARY KEY,
student_name VARCHAR(100),
course1 VARCHAR(50),
course2 VARCHAR(50),
course3 VARCHAR(50),
grade1 DECIMAL(5,2),
grade2 DECIMAL(5,2),
grade3 DECIMAL(5,2)
);
Example: Satisfying 1NF
-- GOOD: Satisfies 1NF
CREATE TABLE students (
student_id INT PRIMARY KEY,
student_name VARCHAR(100) NOT NULL
);
CREATE TABLE enrollments (
student_id INT,
course_id INT,
grade DECIMAL(5,2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id)
);
Second Normal Form (2NF)
Rules: 1. Must be in 1NF 2. All non-key attributes must be fully functionally dependent on the primary key 3. No partial dependencies
Example: Violating 2NF
-- BAD: Violates 2NF - partial dependency
CREATE TABLE bad_enrollments (
student_id INT,
course_id INT,
student_name VARCHAR(100), -- Depends only on student_id
course_name VARCHAR(100), -- Depends only on course_id
grade DECIMAL(5,2), -- Depends on both student_id and course_id
PRIMARY KEY (student_id, course_id)
);
Example: Satisfying 2NF
-- GOOD: Satisfies 2NF
CREATE TABLE students (
student_id INT PRIMARY KEY,
student_name VARCHAR(100) NOT NULL
);
CREATE TABLE courses (
course_id INT PRIMARY KEY,
course_name VARCHAR(100) NOT NULL
);
CREATE TABLE enrollments (
student_id INT,
course_id INT,
grade DECIMAL(5,2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id)
);
Third Normal Form (3NF)
Rules: 1. Must be in 2NF 2. No transitive dependencies 3. All non-key attributes must be non-transitively dependent on the primary key
Example: Violating 3NF
-- BAD: Violates 3NF - transitive dependency
CREATE TABLE bad_employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(100),
dept_id INT,
dept_name VARCHAR(100), -- Transitive dependency: dept_name depends on dept_id
dept_location VARCHAR(100), -- Transitive dependency: dept_location depends on dept_id
salary DECIMAL(10,2)
);
Example: Satisfying 3NF
-- GOOD: Satisfies 3NF
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(100) NOT NULL,
dept_location VARCHAR(100)
);
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(100) NOT NULL,
dept_id INT,
salary DECIMAL(10,2),
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);
Boyce-Codd Normal Form (BCNF)
Rules: 1. Must be in 3NF 2. Every determinant must be a candidate key
Example: Violating BCNF
-- BAD: Violates BCNF
CREATE TABLE bad_course_instructors (
course_id INT,
instructor_id INT,
instructor_name VARCHAR(100), -- instructor_name depends on instructor_id
PRIMARY KEY (course_id, instructor_id)
);
-- Problem: instructor_name depends on instructor_id, but instructor_id is not a candidate key
Example: Satisfying BCNF
-- GOOD: Satisfies BCNF
CREATE TABLE instructors (
instructor_id INT PRIMARY KEY,
instructor_name VARCHAR(100) NOT NULL
);
CREATE TABLE course_instructors (
course_id INT,
instructor_id INT,
PRIMARY KEY (course_id, instructor_id),
FOREIGN KEY (instructor_id) REFERENCES instructors(instructor_id)
);
Higher Normal Forms
Fourth Normal Form (4NF)
- Eliminates multi-valued dependencies
- Each multi-valued dependency must be a functional dependency
Fifth Normal Form (5NF)
- Eliminates join dependencies
- Every join dependency must be implied by candidate keys
Data Types and Storage
Choosing Appropriate Data Types
Numeric Types
-- Integer Types
TINYINT -- -128 to 127 (1 byte)
SMALLINT -- -32,768 to 32,767 (2 bytes)
MEDIUMINT -- -8,388,608 to 8,388,607 (3 bytes)
INT -- -2,147,483,648 to 2,147,483,647 (4 bytes)
BIGINT -- Very large integers (8 bytes)
-- When to use each:
CREATE TABLE user_preferences (
user_id INT, -- Use INT for primary keys
age TINYINT UNSIGNED, -- Use TINYINT for small numbers (0-255)
population_count BIGINT, -- Use BIGINT for very large numbers
rating TINYINT UNSIGNED -- Use TINYINT for ratings (1-5)
);
-- Decimal Types
DECIMAL(10,2) -- Fixed-point: 99999999.99 (exact precision)
FLOAT -- Single precision floating point (approximate)
DOUBLE -- Double precision floating point (approximate)
-- When to use each:
CREATE TABLE financial_data (
account_id INT,
balance DECIMAL(15,2), -- Use DECIMAL for money (exact precision)
interest_rate DECIMAL(5,4), -- Use DECIMAL for percentages
temperature FLOAT, -- Use FLOAT for measurements (approximate OK)
scientific_value DOUBLE -- Use DOUBLE for scientific calculations
);
String Types
-- Variable Length Strings
VARCHAR(255) -- Variable length, up to 65,535 characters
TEXT -- Up to 65,535 characters
MEDIUMTEXT -- Up to 16,777,215 characters
LONGTEXT -- Up to 4,294,967,295 characters
-- Fixed Length Strings
CHAR(10) -- Always exactly 10 characters
-- When to use each:
CREATE TABLE user_data (
user_id INT,
username VARCHAR(50), -- Use VARCHAR for variable-length strings
email VARCHAR(255), -- Use VARCHAR for emails
bio TEXT, -- Use TEXT for long descriptions
country_code CHAR(2), -- Use CHAR for fixed-length codes
postal_code CHAR(5) -- Use CHAR for fixed-length codes
);
Date and Time Types
-- Date and Time Types
DATE -- YYYY-MM-DD (3 bytes)
TIME -- HH:MM:SS (3 bytes)
DATETIME -- YYYY-MM-DD HH:MM:SS (8 bytes)
TIMESTAMP -- YYYY-MM-DD HH:MM:SS (4 bytes, timezone aware)
YEAR -- YYYY (1 byte)
-- When to use each:
CREATE TABLE events (
event_id INT,
event_name VARCHAR(100),
event_date DATE, -- Use DATE for dates only
start_time TIME, -- Use TIME for time only
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Use TIMESTAMP for automatic timestamps
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
birth_year YEAR -- Use YEAR for year-only data
);
Special Types
-- ENUM and SET
ENUM('small', 'medium', 'large') -- Choose one value from list
SET('red', 'green', 'blue') -- Choose multiple values from list
-- JSON (MySQL 5.7+)
JSON -- Store JSON data
-- When to use each:
CREATE TABLE products (
product_id INT,
name VARCHAR(100),
size ENUM('XS', 'S', 'M', 'L', 'XL'), -- Use ENUM for predefined choices
colors SET('red', 'green', 'blue', 'yellow'), -- Use SET for multiple choices
attributes JSON, -- Use JSON for flexible data
status ENUM('active', 'inactive', 'discontinued') DEFAULT 'active'
);
Storage Optimization
Choosing Efficient Data Types
-- BAD: Inefficient storage
CREATE TABLE inefficient_table (
id INT, -- Could be SMALLINT if < 32,767
status VARCHAR(10), -- Could be ENUM
flag VARCHAR(3), -- Could be TINYINT
description TEXT, -- Could be VARCHAR if < 255 chars
created_date DATETIME -- Could be DATE if time not needed
);
-- GOOD: Efficient storage
CREATE TABLE efficient_table (
id SMALLINT, -- Use smallest type that fits
status ENUM('active', 'inactive'), -- Use ENUM for predefined values
flag TINYINT, -- Use TINYINT for boolean-like values
description VARCHAR(200), -- Use VARCHAR for shorter text
created_date DATE -- Use DATE if time component not needed
);
Indexing Strategies
Types of Indexes
Primary Key Index
-- Automatically created for PRIMARY KEY
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT, -- Primary key index
username VARCHAR(50) UNIQUE, -- Unique index
email VARCHAR(100)
);
Unique Index
-- Ensures uniqueness and provides fast lookups
CREATE UNIQUE INDEX idx_email ON users(email);
CREATE UNIQUE INDEX idx_username ON users(username);
Regular Index
-- Improves query performance for frequently searched columns
CREATE INDEX idx_last_name ON users(last_name);
CREATE INDEX idx_created_at ON users(created_at);
Composite Index
-- Index on multiple columns
CREATE INDEX idx_name_location ON users(last_name, city);
CREATE INDEX idx_status_date ON orders(status, order_date);
Partial Index
-- Index only on rows that meet certain conditions
CREATE INDEX idx_active_users ON users(username) WHERE status = 'active';
CREATE INDEX idx_recent_orders ON orders(order_id) WHERE order_date >= '2023-01-01';
Index Design Best Practices
When to Create Indexes
-- 1. Primary and Foreign Keys (automatic)
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT, -- Indexed automatically
customer_id INT,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id) -- Indexed automatically
);
-- 2. Frequently searched columns
CREATE INDEX idx_customer_email ON customers(email);
CREATE INDEX idx_product_category ON products(category_id);
-- 3. Columns used in WHERE clauses
CREATE INDEX idx_order_status ON orders(status);
CREATE INDEX idx_user_active ON users(is_active);
-- 4. Columns used in ORDER BY
CREATE INDEX idx_order_date ON orders(order_date);
CREATE INDEX idx_product_price ON products(price);
-- 5. Columns used in JOIN conditions
CREATE INDEX idx_order_customer ON orders(customer_id);
CREATE INDEX idx_order_item_product ON order_items(product_id);
When NOT to Create Indexes
-- 1. Small tables (< 1000 rows)
-- 2. Columns with low cardinality (few unique values)
-- 3. Columns that are frequently updated
-- 4. Columns used only in SELECT (not WHERE/ORDER BY/JOIN)
-- Example of unnecessary index:
CREATE TABLE small_table (
id INT PRIMARY KEY,
status ENUM('active', 'inactive') -- Don't index if only 2 values
);
-- CREATE INDEX idx_status ON small_table(status); -- Unnecessary
Design Patterns and Best Practices
Common Design Patterns
1. Audit Trail Pattern
-- Track changes to important data
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by INT,
updated_by INT
);
-- Audit log table
CREATE TABLE user_audit_log (
audit_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
action ENUM('INSERT', 'UPDATE', 'DELETE'),
old_values JSON,
new_values JSON,
changed_by INT,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
2. Soft Delete Pattern
-- Instead of physically deleting records, mark them as deleted
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
is_deleted BOOLEAN DEFAULT FALSE,
deleted_at TIMESTAMP NULL,
deleted_by INT NULL
);
-- Query active products
SELECT * FROM products WHERE is_deleted = FALSE;
-- Query all products including deleted
SELECT * FROM products;
3. Hierarchical Data Pattern
-- Self-referencing table for hierarchical data
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
parent_id INT NULL,
level INT NOT NULL DEFAULT 0,
path VARCHAR(500), -- Materialized path
FOREIGN KEY (parent_id) REFERENCES categories(category_id)
);
-- Query all children of a category
SELECT * FROM categories WHERE path LIKE '1/2/%';
4. Polymorphic Association Pattern
-- One table can reference multiple other tables
CREATE TABLE comments (
comment_id INT PRIMARY KEY AUTO_INCREMENT,
content TEXT NOT NULL,
commentable_type ENUM('post', 'photo', 'video'),
commentable_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Index for polymorphic queries
CREATE INDEX idx_commentable ON comments(commentable_type, commentable_id);
Naming Conventions
Table Names
-- Use plural nouns, lowercase with underscores
CREATE TABLE users (...);
CREATE TABLE order_items (...);
CREATE TABLE user_profiles (...);
Column Names
-- Use descriptive names, lowercase with underscores
CREATE TABLE users (
user_id INT PRIMARY KEY, -- Primary key: table_name + _id
first_name VARCHAR(50), -- Descriptive names
last_name VARCHAR(50),
email_address VARCHAR(100), -- Clear and specific
created_at TIMESTAMP, -- Timestamps: action + _at
updated_at TIMESTAMP,
is_active BOOLEAN -- Boolean: is_ + description
);
Index Names
-- Use descriptive prefixes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
CREATE UNIQUE INDEX uk_users_username ON users(username);
Performance Considerations
Query Optimization
Efficient Query Design
-- BAD: Inefficient query
SELECT * FROM users u
JOIN orders o ON u.user_id = o.user_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE u.created_at > '2023-01-01';
-- GOOD: Efficient query
SELECT u.user_id, u.username, COUNT(o.order_id) as order_count
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE u.created_at > '2023-01-01'
GROUP BY u.user_id, u.username;
Proper Use of Indexes
-- Create indexes for common query patterns
CREATE INDEX idx_users_created_active ON users(created_at, is_active);
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status);
-- Query that uses the index efficiently
SELECT * FROM users
WHERE created_at > '2023-01-01' AND is_active = TRUE;
Denormalization Strategies
Sometimes, denormalization is necessary for performance:
Read-Heavy Applications
-- Denormalize frequently accessed data
CREATE TABLE order_summary (
order_id INT PRIMARY KEY,
customer_name VARCHAR(100), -- Denormalized from customers table
total_amount DECIMAL(10,2), -- Denormalized calculated field
item_count INT, -- Denormalized count
order_date DATE
);
Reporting Tables
-- Pre-calculated reporting data
CREATE TABLE daily_sales_summary (
date DATE PRIMARY KEY,
total_orders INT,
total_revenue DECIMAL(12,2),
average_order_value DECIMAL(10,2),
top_product_id INT
);
Real-World Design Examples
Example 1: E-commerce Database
-- E-commerce database design
CREATE DATABASE ecommerce;
USE ecommerce;
-- Users and Authentication
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
phone VARCHAR(20),
date_of_birth DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE
);
-- User Addresses
CREATE TABLE user_addresses (
address_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
address_type ENUM('billing', 'shipping') DEFAULT 'shipping',
street_address VARCHAR(255) NOT NULL,
city VARCHAR(100) NOT NULL,
state VARCHAR(50) NOT NULL,
postal_code VARCHAR(20) NOT NULL,
country VARCHAR(50) NOT NULL DEFAULT 'USA',
is_default BOOLEAN DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
);
-- Categories (Hierarchical)
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
description TEXT,
parent_id INT NULL,
level INT NOT NULL DEFAULT 0,
path VARCHAR(500),
is_active BOOLEAN DEFAULT TRUE,
FOREIGN KEY (parent_id) REFERENCES categories(category_id)
);
-- Products
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
sku VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
short_description VARCHAR(500),
category_id INT,
brand VARCHAR(100),
price DECIMAL(10,2) NOT NULL,
cost DECIMAL(10,2),
weight DECIMAL(8,3),
dimensions JSON, -- {length, width, height}
images JSON, -- Array of image URLs
specifications JSON,
inventory_count INT DEFAULT 0,
min_stock_level INT DEFAULT 5,
is_active BOOLEAN DEFAULT TRUE,
is_featured BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);
-- Product Variants (size, color, etc.)
CREATE TABLE product_variants (
variant_id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT NOT NULL,
sku VARCHAR(50) UNIQUE NOT NULL,
variant_name VARCHAR(100) NOT NULL,
attributes JSON, -- {size: 'L', color: 'red'}
price_adjustment DECIMAL(10,2) DEFAULT 0.00,
inventory_count INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE
);
-- Shopping Cart
CREATE TABLE cart_items (
cart_item_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
product_id INT,
variant_id INT NULL,
quantity INT NOT NULL DEFAULT 1,
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
FOREIGN KEY (variant_id) REFERENCES product_variants(variant_id) ON DELETE CASCADE
);
-- Orders
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(20) UNIQUE NOT NULL,
user_id INT NOT NULL,
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded') DEFAULT 'pending',
subtotal DECIMAL(10,2) NOT NULL,
tax_amount DECIMAL(10,2) DEFAULT 0.00,
shipping_amount DECIMAL(10,2) DEFAULT 0.00,
discount_amount DECIMAL(10,2) DEFAULT 0.00,
total_amount DECIMAL(10,2) NOT NULL,
payment_status ENUM('pending', 'paid', 'failed', 'refunded') DEFAULT 'pending',
payment_method VARCHAR(50),
shipping_address JSON,
billing_address JSON,
notes TEXT,
shipped_at TIMESTAMP NULL,
delivered_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- Order Items
CREATE TABLE order_items (
order_item_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
variant_id INT NULL,
product_name VARCHAR(200) NOT NULL, -- Denormalized for historical accuracy
variant_name VARCHAR(100),
sku VARCHAR(50) NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(product_id),
FOREIGN KEY (variant_id) REFERENCES product_variants(variant_id)
);
-- Reviews
CREATE TABLE product_reviews (
review_id INT PRIMARY KEY AUTO_INCREMENT,
product_id INT NOT NULL,
user_id INT NOT NULL,
order_id INT NULL, -- Optional: link to order
rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
title VARCHAR(200),
content TEXT,
is_verified_purchase BOOLEAN DEFAULT FALSE,
is_approved BOOLEAN DEFAULT FALSE,
helpful_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE SET NULL,
UNIQUE KEY unique_user_product_review (user_id, product_id)
);
-- Create Indexes for Performance
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_active ON products(is_active);
CREATE INDEX idx_products_featured ON products(is_featured);
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_created ON orders(created_at);
CREATE INDEX idx_order_items_order ON order_items(order_id);
CREATE INDEX idx_order_items_product ON order_items(product_id);
CREATE INDEX idx_reviews_product ON product_reviews(product_id);
CREATE INDEX idx_reviews_rating ON product_reviews(rating);
CREATE INDEX idx_cart_user ON cart_items(user_id);
Example 2: Content Management System
-- CMS Database Design
CREATE DATABASE cms;
USE cms;
-- Users and Roles
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
avatar_url VARCHAR(255),
bio TEXT,
is_active BOOLEAN DEFAULT TRUE,
last_login TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Roles and Permissions
CREATE TABLE roles (
role_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
permissions JSON -- Array of permission strings
);
CREATE TABLE user_roles (
user_id INT,
role_id INT,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
assigned_by INT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
FOREIGN KEY (assigned_by) REFERENCES users(user_id)
);
-- Content Types
CREATE TABLE content_types (
content_type_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
label VARCHAR(100) NOT NULL,
description TEXT,
fields JSON, -- Field definitions
is_active BOOLEAN DEFAULT TRUE
);
-- Categories
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
parent_id INT NULL,
level INT NOT NULL DEFAULT 0,
path VARCHAR(500),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories(category_id)
);
-- Tags
CREATE TABLE tags (
tag_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
slug VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
usage_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Content
CREATE TABLE content (
content_id INT PRIMARY KEY AUTO_INCREMENT,
content_type_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
excerpt TEXT,
body LONGTEXT,
author_id INT NOT NULL,
status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
featured_image_url VARCHAR(255),
meta_title VARCHAR(255),
meta_description TEXT,
published_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (content_type_id) REFERENCES content_types(content_type_id),
FOREIGN KEY (author_id) REFERENCES users(user_id)
);
-- Content Categories (Many-to-Many)
CREATE TABLE content_categories (
content_id INT,
category_id INT,
PRIMARY KEY (content_id, category_id),
FOREIGN KEY (content_id) REFERENCES content(content_id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE CASCADE
);
-- Content Tags (Many-to-Many)
CREATE TABLE content_tags (
content_id INT,
tag_id INT,
PRIMARY KEY (content_id, tag_id),
FOREIGN KEY (content_id) REFERENCES content(content_id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id) ON DELETE CASCADE
);
-- Comments
CREATE TABLE comments (
comment_id INT PRIMARY KEY AUTO_INCREMENT,
content_id INT NOT NULL,
parent_id INT NULL, -- For nested comments
author_name VARCHAR(100) NOT NULL,
author_email VARCHAR(100) NOT NULL,
author_url VARCHAR(255),
content TEXT NOT NULL,
status ENUM('pending', 'approved', 'spam', 'trash') DEFAULT 'pending',
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (content_id) REFERENCES content(content_id) ON DELETE CASCADE,
FOREIGN KEY (parent_id) REFERENCES comments(comment_id) ON DELETE CASCADE
);
-- Media Library
CREATE TABLE media (
media_id INT PRIMARY KEY AUTO_INCREMENT,
filename VARCHAR(255) NOT NULL,
original_filename VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INT NOT NULL,
mime_type VARCHAR(100) NOT NULL,
alt_text VARCHAR(255),
caption TEXT,
uploaded_by INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (uploaded_by) REFERENCES users(user_id)
);
-- Create Indexes
CREATE INDEX idx_content_type ON content(content_type_id);
CREATE INDEX idx_content_author ON content(author_id);
CREATE INDEX idx_content_status ON content(status);
CREATE INDEX idx_content_published ON content(published_at);
CREATE INDEX idx_content_slug ON content(slug);
CREATE INDEX idx_comments_content ON comments(content_id);
CREATE INDEX idx_comments_status ON comments(status);
CREATE INDEX idx_media_uploader ON media(uploaded_by);
CREATE INDEX idx_categories_parent ON categories(parent_id);
CREATE INDEX idx_categories_active ON categories(is_active);
Hands-on Exercise
Exercise 1: Design a Library Management System
Design a complete database schema for a library management system with the following requirements:
- Books: title, author, ISBN, publication year, genre, copies available
- Members: name, email, phone, membership type, join date
- Borrowing: member borrows book, due date, return date, fine amount
- Staff: librarians with different access levels
- Reservations: members can reserve books
Exercise 2: Normalization Practice
Given the following denormalized table, normalize it to 3NF:
CREATE TABLE bad_employee_data (
emp_id INT,
emp_name VARCHAR(100),
dept_id INT,
dept_name VARCHAR(100),
dept_location VARCHAR(100),
manager_id INT,
manager_name VARCHAR(100),
project_id INT,
project_name VARCHAR(100),
project_start_date DATE,
project_end_date DATE,
hours_worked INT,
hourly_rate DECIMAL(10,2)
);
Exercise 3: Performance Optimization
Design indexes for the following query patterns:
- Find all orders by customer in date range
- Find products by category and price range
- Find users who haven't logged in recently
- Generate monthly sales reports
Key Takeaways
- Design for the future: Consider scalability and flexibility
- Normalize appropriately: Balance normalization with performance needs
- Choose data types wisely: Match data types to actual data requirements
- Index strategically: Create indexes for common query patterns
- Document everything: Clear naming and documentation are essential
- Test thoroughly: Validate design with realistic data volumes
- Plan for maintenance: Include audit trails and soft deletes where appropriate
Next Steps
In the next lesson, we'll learn about advanced SQL features including views, stored procedures, and triggers.
Practice Questions
- What are the main benefits of database normalization?
- When might you choose to denormalize data?
- How do you determine the appropriate data type for a column?
- What factors should you consider when creating indexes?
- How do you handle hierarchical data in a relational database?