Skip to main content
MySQL Basics
CHAPTER 29 Beginner

Database Design Project (E-Commerce)

Updated: May 16, 2026
7 min read

# CHAPTER 29

Database Design Project (E-Commerce)

1. Introduction

You have learned the theory. You have mastered the syntax. Now, you must act as the Lead Database Architect. A client wants to build an E-Commerce platform (like Amazon or Shopify). If you design the database poorly, the company will face data corruption, impossible reporting, and catastrophic performance issues. In this chapter, we synthesize everything we've learned to design a fully normalized, production-grade E-Commerce schema.

2. Learning Objectives

By the end of this project, you will be able to:
  • Identify the core entities required for an E-Commerce system.
  • Apply Database Normalization rules to prevent redundancy.
  • Map One-to-Many and Many-to-Many relationships.
  • Write the final CREATE TABLE scripts with full constraints.

3. Step 1: Identifying the Entities

Before writing any SQL, we grab a whiteboard and list the core "Nouns" (Entities) of our business:
  1. 1. Users: The customers making purchases.
  1. 2. Products: The items being sold.
  1. 3. Categories: The groupings of products (e.g., Electronics, Clothing).
  1. 4. Orders: The master record of a purchase.
  1. 5. Order_Items: The specific products attached to a specific order.

4. Step 2: Mapping the Relationships

How do these entities mathematically connect?
  • A Category has Many Products. A Product belongs to One Category. (1:N)
  • A User has Many Orders. An Order belongs to One User. (1:N)
  • An Order has Many Products. A Product can be in Many Orders. (N:M) -> *We must use the order_items Pivot Table!*

5. Step 3: Writing the DDL (Data Definition Language)

We will write the tables in the exact order necessary to satisfy Foreign Key constraints. (You must create the Parent before you can create the Child!).

#### The Parents (No Foreign Keys)

sql
12345678910111213141516
CREATE DATABASE storefront_db;
USE storefront_db;

-- 1. USERS TABLE
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(150) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 2. CATEGORIES TABLE
CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);

#### The Children (1:N Relationships)

sql
1234567891011121314151617181920212223242526
-- 3. PRODUCTS TABLE (Belongs to Categories)
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    category_id INT NOT NULL,
    name VARCHAR(150) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock INT DEFAULT 0,
    
    -- Constraint: Price cannot be negative!
    CONSTRAINT chk_price CHECK (price >= 0),
    
    -- Relationship
    FOREIGN KEY (category_id) REFERENCES categories(id)
);

-- 4. ORDERS TABLE (Belongs to Users)
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    total_amount DECIMAL(10,2) NOT NULL,
    status ENUM('Pending', 'Shipped', 'Delivered', 'Cancelled') DEFAULT 'Pending',
    order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    -- Relationship
    FOREIGN KEY (user_id) REFERENCES users(id)
);

#### The Pivot Table (N:M Relationship)

sql
1234567891011121314
-- 5. ORDER_ITEMS TABLE (Bridges Orders and Products)
CREATE TABLE order_items (
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL DEFAULT 1,
    price_at_purchase DECIMAL(10,2) NOT NULL,
    
    -- Composite Primary Key (An order can't have two identical product lines)
    PRIMARY KEY (order_id, product_id),
    
    -- Relationships
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products(id)
);

6. Architectural Brilliance: price_at_purchase

Why did we put a price_at_purchase column inside the order_items table? Can't we just JOIN to the products table to see the price? NO! If John buys a Laptop today for $1,000, and tomorrow the Admin raises the price to $1,200... if John looks at his receipt history, it will say he paid $1,200! This violates accounting laws. By explicitly saving the price_at_purchase in the pivot table *at the exact moment of the transaction*, we create an immutable financial record that survives future price changes.

7. Step 4: The Ultimate Reporting Query

Let's prove our architecture works. The CEO asks: *"Give me a list of all shipped orders over $500, including the customer's email and the names of the products they bought."*
sql
1234567891011
SELECT 
    o.id AS OrderNumber, 
    u.email AS Customer, 
    p.name AS Product, 
    oi.quantity, 
    o.total_amount AS OrderTotal
FROM orders o
INNER JOIN users u ON o.user_id = u.id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.status = 'Shipped' AND o.total_amount > 500;

*Because our Foreign Keys are perfectly mapped, this 4-table join executes flawlessly.*

8. Common Mistakes

  • Storing calculated totals blindly: While we store total_amount in the orders table for caching/speed, you must ensure that your backend PHP code strictly calculates that total by summing up the order_items. Never trust a total passed directly from the frontend HTML cart!

9. Best Practices

  • Indexes for Speed: In production, you would immediately add an index to user_id in the orders table, because the most common query on the website will be: "Show me My Orders."

10. Exercises

  1. 1. Analyze the schema: If a database admin attempts to DELETE a category that currently has 50 products assigned to it, what will MySQL do?
  1. 2. Write the SQL query to find the total sum of revenue generated by all orders that are currently marked as 'Delivered'.

11. MCQ Quiz with Answers

Question 1

In the E-Commerce schema provided, why is the order_items table strictly necessary?

Question 2

Why is it a critical architectural best practice to store the price_at_purchase inside the order_items table, rather than just referencing the main products table?

12. Interview Questions

  • Q: Walk me through your database design for a basic E-Commerce cart system. Detail the relationships between Users, Orders, and Products, and specifically explain how you track historical pricing.
  • Q: Look at the ON DELETE CASCADE rule applied to the order_id in the order_items table. Explain what this does and why it was not applied to the product_id constraint.

13. FAQs

Q: How do we handle shopping carts before the user checks out? A: You can either store active Carts in a temporary NoSQL database (like Redis) for blistering speed, or create a carts and cart_items table in MySQL that perfectly mirrors the structure of the Orders tables!

14. Summary

You have evolved from a student to an Architect. By systematically identifying entities, enforcing strict Foreign Key relationships, protecting data with constraints, and anticipating real-world business logic (like historical pricing), you are fully capable of designing robust, enterprise-grade database schemas.

15. Next Chapter Recommendation

You have reached the end of the journey. In Chapter 30: Course Summary and Next Steps, we will review the massive scope of your new capabilities, discuss industry certifications, and outline your path toward becoming a Senior Backend Developer or Database Administrator.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·