Complete solution with ER diagram, SQL schema, and implementation details.
For an online bookstore, identify:
Entities to consider: Books, Authors, Customers, Orders, Categories, Reviews
Core entities:
Junction tables (for many-to-many relationships):
| Relationship | Type | Implementation |
|---|---|---|
| Books - Authors | Many-to-Many | BOOKS_AUTHORS junction table |
| Books - Categories | Many-to-Many | BOOKS_CATEGORIES junction table |
| Customers - Orders | One-to-Many | ORDERS.customer_id FK |
| Orders - Books | Many-to-Many | ORDER_ITEMS junction table |
| Customers - Reviews | One-to-Many | REVIEWS.customer_id FK |
| Books - Reviews | One-to-Many | REVIEWS.book_id FK |
-- CUSTOMERS
CREATE TABLE CUSTOMERS (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
shipping_address TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (email)
) ENGINE=InnoDB;
-- AUTHORS
CREATE TABLE AUTHORS (
author_id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
biography TEXT,
INDEX idx_name (last_name, first_name)
) ENGINE=InnoDB;
-- CATEGORIES
CREATE TABLE CATEGORIES (
category_id INT AUTO_INCREMENT PRIMARY KEY,
category_name VARCHAR(100) UNIQUE NOT NULL,
description TEXT
) ENGINE=InnoDB;
-- BOOKS
CREATE TABLE BOOKS (
book_id INT AUTO_INCREMENT PRIMARY KEY,
isbn VARCHAR(13) UNIQUE NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT DEFAULT 0,
publisher VARCHAR(100),
publication_date DATE,
CHECK (price >= 0),
CHECK (stock_quantity >= 0),
INDEX idx_title (title),
INDEX idx_isbn (isbn)
) ENGINE=InnoDB;
-- BOOKS_AUTHORS
CREATE TABLE BOOKS_AUTHORS (
book_id INT NOT NULL,
author_id INT NOT NULL,
author_order INT DEFAULT 1,
PRIMARY KEY (book_id, author_id),
FOREIGN KEY (book_id) REFERENCES BOOKS(book_id)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (author_id) REFERENCES AUTHORS(author_id)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
-- BOOKS_CATEGORIES
CREATE TABLE BOOKS_CATEGORIES (
book_id INT NOT NULL,
category_id INT NOT NULL,
PRIMARY KEY (book_id, category_id),
FOREIGN KEY (book_id) REFERENCES BOOKS(book_id)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (category_id) REFERENCES CATEGORIES(category_id)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
-- ORDERS
CREATE TABLE ORDERS (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2) NOT NULL,
status VARCHAR(50) DEFAULT 'Pending',
CHECK (total_amount >= 0),
CHECK (status IN ('Pending', 'Processing', 'Shipped', 'Delivered', 'Cancelled')),
FOREIGN KEY (customer_id) REFERENCES CUSTOMERS(customer_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
INDEX idx_customer (customer_id),
INDEX idx_status (status)
) ENGINE=InnoDB;
-- ORDER_ITEMS
CREATE TABLE ORDER_ITEMS (
order_item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
book_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
price_at_purchase DECIMAL(10,2) NOT NULL,
CHECK (quantity > 0),
CHECK (price_at_purchase >= 0),
FOREIGN KEY (order_id) REFERENCES ORDERS(order_id)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (book_id) REFERENCES BOOKS(book_id)
ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB;
-- REVIEWS
CREATE TABLE REVIEWS (
review_id INT AUTO_INCREMENT PRIMARY KEY,
book_id INT NOT NULL,
customer_id INT NOT NULL,
rating INT NOT NULL,
review_text TEXT,
review_date DATETIME DEFAULT CURRENT_TIMESTAMP,
CHECK (rating BETWEEN 1 AND 5),
UNIQUE KEY (book_id, customer_id),
FOREIGN KEY (book_id) REFERENCES BOOKS(book_id)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (customer_id) REFERENCES CUSTOMERS(customer_id)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
All tables use surrogate keys (auto-increment integers) rather than natural keys. This provides:
ORDER_ITEMS.price_at_purchase stores the book price at time of purchase. This is necessary because book prices may change over time, but historical orders must reflect the actual price paid.
| Relationship | ON DELETE | Reason |
|---|---|---|
| ORDERS → CUSTOMERS | RESTRICT | Preserve order history, prevent accidental customer deletion |
| ORDER_ITEMS → ORDERS | CASCADE | Order items have no meaning without parent order |
| ORDER_ITEMS → BOOKS | RESTRICT | Cannot delete books that appear in orders |