DataBase Design Lesson 13 – One-to-Many Relationships | Dataplexa
ER Design · Lesson 13

One-to-Many Relationships

Master the most common database relationship by designing foreign keys, understanding cardinality constraints, and implementing referential integrity in BookVault's order system.

What Makes One-to-Many Special

One-to-many relationships dominate real-world databases. Think about it: one customer places many orders. One author writes many books. One category contains many products.

Amazon runs on thousands of one-to-many relationships. Each customer has multiple orders, wishlists, and reviews. Each product belongs to one seller but gets many reviews. The relationship appears everywhere because it mirrors how we organize information naturally.

But here's what trips up most beginners: the foreign key always goes on the "many" side. Always. No exceptions. Why? Because putting it on the "one" side would mean storing multiple values in a single field — database suicide.

Data Insight

One-to-many relationships handle 80% of all business data connections. Master this pattern and you control most database design challenges.

The Foreign Key Foundation

A foreign key creates the bridge between tables. It's a column that references the primary key of another table. Think of it as a permanent address — it tells the database exactly where to find related information.

BookVault's order system demonstrates this perfectly. The orders table needs a customer_id foreign key pointing to the customers table. Each order belongs to exactly one customer, but customers can have multiple orders.

CUSTOMERS
Attributes
PK customer_id
first_name
last_name
email
city
created_at
ONE
places
MANY
ORDERS
Attributes
PK order_id
FK customer_id
order_date
status
total_amount

Creating the Foreign Key

-- Create customers table first (parent table)
-- BookVault customer data
CREATE TABLE customers (
    customer_id INT PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    city VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Table 'customers' created successfully

What just happened?

We created the parent table first — this is mandatory. The customer_id acts as the primary key that other tables will reference. Try this: always create parent tables before child tables to avoid foreign key errors.

-- Create orders table with foreign key constraint
-- BookVault order tracking system
CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date DATE NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    total_amount DECIMAL(10,2) NOT NULL,
    
    -- Foreign key constraint ensures data integrity
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
Table 'orders' created successfully
Foreign key constraint 'orders_ibfk_1' added

What just happened?

The FOREIGN KEY constraint creates the relationship. Now the database automatically prevents orphaned orders — you can't create an order for a customer that doesn't exist. Try this: attempt to insert an order with customer_id = 999 and watch it fail.

Cardinality and Participation

Cardinality describes how many instances can exist on each side of the relationship. In one-to-many, the "one" side has exactly one record, while the "many" side can have zero, one, or multiple records.

Participation determines whether the relationship is mandatory or optional. Can a customer exist without orders? Can an order exist without a customer? These business rules shape your database design.

Total Participation

Every order MUST have a customer. Orders cannot exist without a customer relationship.

Partial Participation

Customers can exist without orders. New customers haven't placed orders yet.

Business Logic

Cardinality enforces business rules. One author writes many books, but each book has one primary author.

Common Mistake

Putting foreign keys on the "one" side creates data redundancy and update anomalies.

Multiple One-to-Many Relationships

Real tables participate in multiple one-to-many relationships simultaneously. BookVault's books table connects to both authors and categories tables. Each book has one author and belongs to one category, but authors write many books and categories contain many books.

AUTHORS
Attributes
PK author_id
first_name
last_name
nationality
ONE
MANY
BOOKS
Attributes
PK book_id
FK author_id
FK category_id
title
price
stock_qty
MANY
ONE
CATEGORIES
Attributes
PK category_id
name
parent_category_id

Building the Complete Schema

-- Create parent tables first
-- BookVault author management
CREATE TABLE authors (
    author_id INT PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    nationality VARCHAR(50)
);

-- BookVault category hierarchy
CREATE TABLE categories (
    category_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    parent_category_id INT,
    FOREIGN KEY (parent_category_id) REFERENCES categories(category_id)
);
Table 'authors' created successfully
Table 'categories' created successfully
Self-referencing foreign key constraint added
-- Create books table with multiple foreign keys
-- BookVault product catalog
CREATE TABLE books (
    book_id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    author_id INT NOT NULL,
    category_id INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    stock_qty INT DEFAULT 0,
    published_year YEAR,
    
    -- Multiple foreign key constraints
    FOREIGN KEY (author_id) REFERENCES authors(author_id),
    FOREIGN KEY (category_id) REFERENCES categories(category_id)
);
Table 'books' created successfully
Foreign key constraints 'books_ibfk_1' and 'books_ibfk_2' added

What just happened?

The books table now has two foreign keys, creating two separate one-to-many relationships. Notice how categories also has a self-referencing foreign key for hierarchical categories. Try this: create "Fiction" and "Science Fiction" categories with the parent-child relationship.

Referential Integrity Actions

What happens when you delete a customer who has orders? Or update an author's ID? Referential integrity actions define how the database handles these scenarios automatically.

Without proper actions, you risk orphaned records — orders pointing to deleted customers, books referencing non-existent authors. Netflix learned this lesson the hard way when content removal broke recommendation algorithms.

Action Behavior Use Case
CASCADE Delete/update child records automatically Delete customer → delete all their orders
SET NULL Set foreign key to NULL Delete author → set books.author_id to NULL
RESTRICT Prevent deletion/update if children exist Can't delete customer with pending orders
SET DEFAULT Set foreign key to default value Delete category → assign books to "General"
-- Create orders with CASCADE delete
-- BookVault order management with referential integrity
CREATE TABLE orders_with_cascade (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date DATE NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    total_amount DECIMAL(10,2) NOT NULL,
    
    -- CASCADE: delete orders when customer is deleted
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id) 
        ON DELETE CASCADE 
        ON UPDATE CASCADE
);
Table 'orders_with_cascade' created successfully
Foreign key constraint with CASCADE actions added

Warning: CASCADE Can Be Dangerous

CASCADE deletes can eliminate massive amounts of data instantly. Deleting one customer might cascade through orders, order_items, and reviews. Test cascade behavior thoroughly in development before deploying to production.

Querying One-to-Many Data

One-to-many relationships unlock powerful query patterns. You can find all orders for a customer, count books per author, or calculate total sales by category. The foreign key creates the bridge for JOIN operations.

-- Sample data for BookVault demo
INSERT INTO customers (first_name, last_name, email, city) 
VALUES 
    ('Sarah', 'Chen', 'sarah.chen@email.com', 'Seattle'),
    ('Marcus', 'Johnson', 'marcus.j@email.com', 'Portland'),
    ('Elena', 'Rodriguez', 'elena.r@email.com', 'San Francisco');

INSERT INTO authors (first_name, last_name, nationality) 
VALUES 
    ('Neil', 'Gaiman', 'British'),
    ('Ursula', 'Le Guin', 'American'),
    ('Haruki', 'Murakami', 'Japanese');

INSERT INTO categories (name) 
VALUES 
    ('Fantasy'),
    ('Science Fiction'),
    ('Literary Fiction');
3 customers inserted
3 authors inserted  
3 categories inserted
-- Find all orders for a specific customer
-- BookVault customer order history
SELECT 
    c.first_name,
    c.last_name,
    o.order_id,
    o.order_date,
    o.total_amount,
    o.status
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE c.email = 'sarah.chen@email.com'
ORDER BY o.order_date DESC;
first_name | last_name | order_id | order_date | total_amount | status
-----------|-----------|----------|------------|--------------|--------
Sarah      | Chen      | 1001     | 2024-01-15 | 89.97        | shipped
Sarah      | Chen      | 1003     | 2024-01-10 | 24.99        | delivered
-- Count books per author (aggregation across one-to-many)
-- BookVault author productivity analysis
SELECT 
    a.first_name,
    a.last_name,
    a.nationality,
    COUNT(b.book_id) as total_books,
    AVG(b.price) as avg_book_price
FROM authors a
LEFT JOIN books b ON a.author_id = b.author_id
GROUP BY a.author_id, a.first_name, a.last_name, a.nationality
ORDER BY total_books DESC;
first_name | last_name | nationality | total_books | avg_book_price
-----------|-----------|-------------|-------------|----------------
Neil       | Gaiman    | British     | 8           | 16.99
Ursula     | Le Guin   | American    | 12          | 14.50
Haruki     | Murakami  | Japanese    | 6           | 18.75

What just happened?

The LEFT JOIN preserves all authors, even those without books. The GROUP BY collapses multiple books per author into summary statistics. Notice how COUNT(b.book_id) counts actual books, not NULLs for authors without books. Try this: change to INNER JOIN and see authors without books disappear.

Pro Tip: Use LEFT JOIN when you want to preserve all records from the "one" side, even if they have no related records on the "many" side. Use INNER JOIN when you only want records that have relationships.

Quiz

1. In BookVault's customer-orders relationship, where should the foreign key be placed?


2. If a BookVault customer is deleted and the foreign key has ON DELETE CASCADE, what happens to their orders?


3. To display all BookVault authors and count their books (including authors with zero books), which JOIN type should you use?


Up Next

Many-to-Many Relationships

Handle complex relationships where both sides can have multiple connections, using junction tables to model BookVault's book-author collaborations and customer wishlists.