Until now, all your data has lived in one or two standalone tables. Real databases have dozens β sometimes hundreds β of tables, all connected to each other. A customer places orders. An order contains books. A book belongs to categories. These connections are called relationships, and understanding them is the key to thinking like a database designer.
π― Learning Objectives
By the end of this lesson, you will be able to:
Explain why data is split across multiple tables
Identify one-to-one, one-to-many, and many-to-many relationships
Read and interpret Entity-Relationship (ER) diagrams
Design a junction table for many-to-many relationships
Create related tables with proper primary and foreign key columns
Data duplication β Alice's name, email, and city are repeated for every order she places. If she orders 50 books, that's 50 copies of the same info.
Update anomalies β If Alice changes her email, you have to update it in every row. Miss one and your data is inconsistent.
Deletion anomalies β If you delete Alice's only order, you lose her customer info entirely.
Insert anomalies β You can't add a new customer until they place an order (or you'd have NULL order fields).
Wasted space β Every row stores redundant data, bloating the table unnecessarily.
The solution: split the data into separate tables and connect them with relationships.
erDiagram
customers {
int id PK
varchar first_name
varchar last_name
varchar email
varchar city
}
orders {
int id PK
int customer_id FK
date order_date
decimal total
}
customers ||--o{ orders : "places"
Now Alice's info is stored once in the customers table. Her orders reference her by ID. Update her email in one place, and it's correct everywhere.
Primary Keys & Foreign Keys
Relationships depend on two key concepts you've already seen:
Primary Key (PK) β The Identifier
Every table should have a primary key β a column (or set of columns) that uniquely identifies each row. You've been using these since Lesson 4:
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY, -- unique identifier
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100)
);
Foreign Key (FK) β The Connector
A foreign key is a column in one table that references the primary key of another table. It's the link that creates the relationship:
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT, -- foreign key
order_date DATE,
total DECIMAL(10, 2),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
The customer_id column in the orders table stores a value that matches an id in the customers table. This is how MySQL knows which customer placed which order.
Foreign key columns are typically named tablename_id β like customer_id, book_id, or order_id. This makes it immediately clear which table they reference.
β Key Terminology
Parent table β the table being referenced (customers)
Child table β the table containing the foreign key (orders)
The foreign key in the child table must match a primary key value in the parent table (or be NULL)
One-to-Many (1:M) Relationships
The one-to-many relationship is by far the most common type in relational databases. One record in the parent table can be linked to many records in the child table, but each child record links to exactly one parent.
erDiagram
customers ||--o{ orders : "places"
customers {
int id PK
varchar first_name
varchar last_name
}
orders {
int id PK
int customer_id FK
date order_date
}
One customer can place many orders, but each order belongs to exactly one customer.
Real-World Examples
One Side (Parent)
Many Side (Child)
Relationship
Customer
Orders
One customer places many orders
Author
Books
One author writes many books
Department
Employees
One department has many employees
Category
Products
One category contains many products
Teacher
Classes
One teacher teaches many classes
Country
Cities
One country has many cities
How to Build It
The foreign key always goes on the "many" side:
-- Parent table (the "one" side)
CREATE TABLE authors (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
birth_year INT
);
-- Child table (the "many" side) β has the foreign key
CREATE TABLE books (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author_id INT NOT NULL,
price DECIMAL(6, 2),
FOREIGN KEY (author_id) REFERENCES authors(id)
);
π‘ How to Identify the "Many" Side
Ask yourself: "Can one [A] have multiple [B]s?" If yes, A is the "one" side and B is the "many" side. The foreign key goes in B's table.
Can one customer have multiple orders? Yes β FK goes in orders.
Can one order have multiple customers? No β customers is the "one" side.
One-to-One (1:1) Relationships
In a one-to-one relationship, each record in Table A is linked to exactly one record in Table B, and vice versa. These are less common and are typically used for:
Splitting a wide table β separating rarely-accessed columns to improve performance
Security β keeping sensitive data in a separate, restricted table
Optional data β information that only some records have
erDiagram
customers ||--|| customer_profiles : "has"
customers {
int id PK
varchar first_name
varchar last_name
varchar email
}
customer_profiles {
int id PK
int customer_id FK
text bio
varchar avatar_url
date date_of_birth
}
Implementation
A 1:1 relationship uses a foreign key with a UNIQUE constraint β this prevents multiple rows from pointing to the same parent:
CREATE TABLE customer_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT UNIQUE NOT NULL, -- UNIQUE enforces 1:1
bio TEXT,
avatar_url VARCHAR(255),
date_of_birth DATE,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
The UNIQUE constraint on customer_id ensures each customer can have at most one profile. Without it, this would be a one-to-many relationship.
Real-World Examples
Table A
Table B
Why Separate?
Users
User Settings
Optional; not all users customize settings
Employees
Payroll Details
Security β restricted access to salary data
Products
Product Details
Performance β large text/blob fields in a separate table
Countries
Capital Cities
Each country has exactly one capital
Many-to-Many (M:M) Relationships
In a many-to-many relationship, records on both sides can link to multiple records on the other side:
One book can belong to many categories (Fiction, Sci-Fi, Classic)
One category can contain many books
You can't represent this with a single foreign key β you'd need multiple values in one column, which breaks relational rules. The solution is a junction table (also called a bridge table, linking table, or join table).
The Junction Table Pattern
erDiagram
books ||--o{ book_categories : "assigned to"
categories ||--o{ book_categories : "contains"
books {
int id PK
varchar title
decimal price
}
categories {
int id PK
varchar name
}
book_categories {
int book_id FK
int category_id FK
}
The junction table book_categories sits between the two main tables. Each row represents one link β "this book is in this category." The two foreign keys together form a composite primary key.
Creating the Tables
-- Table A
CREATE TABLE books (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
price DECIMAL(6, 2)
);
-- Table B
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
-- Junction table (the bridge)
CREATE TABLE book_categories (
book_id INT NOT NULL,
category_id INT NOT NULL,
PRIMARY KEY (book_id, category_id), -- composite PK
FOREIGN KEY (book_id) REFERENCES books(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
Inserting Data
-- Add some categories
INSERT INTO categories (name) VALUES
('Fiction'), ('Science Fiction'), ('Classic'), ('Dystopian');
-- Link books to categories (a book can have multiple categories)
INSERT INTO book_categories (book_id, category_id) VALUES
(1, 1), -- Dune β Fiction
(1, 2), -- Dune β Science Fiction
(2, 1), -- 1984 β Fiction
(2, 4), -- 1984 β Dystopian
(2, 3), -- 1984 β Classic
(3, 1), -- Pride and Prejudice β Fiction
(3, 3); -- Pride and Prejudice β Classic
π‘ Composite Primary Key
PRIMARY KEY (book_id, category_id) means the combination of both columns must be unique. A book can be in multiple categories, and a category can have multiple books β but the same bookβcategory pair can only appear once.
Real-World Examples
Table A
Table B
Junction Table
Students
Courses
enrollments
Orders
Books
order_items
Actors
Movies
movie_cast
Users
Roles
user_roles
Recipes
Ingredients
recipe_ingredients
Playlists
Songs
playlist_songs
Junction Tables with Extra Data
Junction tables often carry additional data about the relationship itself:
-- Order items β the junction between orders and books
CREATE TABLE order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
book_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1, -- extra data!
price_at_time DECIMAL(6, 2) NOT NULL, -- extra data!
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (book_id) REFERENCES books(id)
);
The quantity and price_at_time columns belong to neither the order nor the book β they describe the relationship between a specific order and a specific book.
β Why Store Price at Time of Order?
Book prices change. If you only stored book_id and looked up the current price, historical orders would show the wrong total. Storing price_at_time captures what the customer actually paid β a common and important pattern.
Reading ER Diagrams
An Entity-Relationship (ER) diagram is a visual blueprint of your database. You've been seeing them throughout this lesson. Here's how to read the notation:
Crow's Foot Notation
The most common ER diagram style uses symbols at the ends of relationship lines:
Symbol
Meaning
Description
||
Exactly one
This side has exactly one record
o|
Zero or one
This side has zero or one record (optional)
}| or {
One or many
This side has one or more records
o{
Zero or many
This side has zero or more records
erDiagram
CUSTOMER ||--o{ ORDER : "places"
ORDER ||--|{ ORDER_ITEM : "contains"
BOOK ||--o{ ORDER_ITEM : "included in"
BOOK }o--o{ CATEGORY : "belongs to"
Reading this diagram:
A customer places zero or many orders (a new customer may have no orders yet)
An order contains one or many order items (an order must have at least one item)
A book appears in zero or many order items (some books may never be ordered)
A book belongs to zero or many categories, and a category contains zero or many books (many-to-many)
π‘ Reading ER Diagrams
Read each line from left to right or right to left, using the notation at each end. The verb on the line describes the relationship. Practice reading the diagram above out loud β "One customer places zero or many orders" β until it becomes natural.
Building a Bookstore Schema
Let's put it all together and design a complete schema for our bookstore. This is what we'll use for the rest of the course:
erDiagram
authors ||--o{ books : "writes"
books ||--o{ order_items : "ordered as"
orders ||--|{ order_items : "contains"
customers ||--o{ orders : "places"
books }o--o{ categories : "tagged"
books ||--o{ book_categories : ""
categories ||--o{ book_categories : ""
authors {
int id PK
varchar name
int birth_year
}
books {
int id PK
varchar title
int author_id FK
decimal price
int pages
date published
boolean in_stock
}
customers {
int id PK
varchar first_name
varchar last_name
varchar email
varchar city
}
orders {
int id PK
int customer_id FK
date order_date
decimal total
}
order_items {
int id PK
int order_id FK
int book_id FK
int quantity
decimal price_at_time
}
categories {
int id PK
varchar name
}
book_categories {
int book_id FK
int category_id FK
}
The Complete SQL
-- 1. Authors (no dependencies)
CREATE TABLE authors (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
birth_year INT
);
-- 2. Books (depends on authors)
CREATE TABLE books (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author_id INT NOT NULL,
price DECIMAL(6, 2) NOT NULL,
pages INT,
published DATE,
in_stock BOOLEAN DEFAULT TRUE,
FOREIGN KEY (author_id) REFERENCES authors(id)
);
-- 3. Categories (no dependencies)
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
-- 4. Book-Category junction (depends on books, categories)
CREATE TABLE book_categories (
book_id INT NOT NULL,
category_id INT NOT NULL,
PRIMARY KEY (book_id, category_id),
FOREIGN KEY (book_id) REFERENCES books(id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
-- 5. Customers (no dependencies)
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
city VARCHAR(50)
);
-- 6. Orders (depends on customers)
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total DECIMAL(10, 2) DEFAULT 0,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- 7. Order items (depends on orders, books)
CREATE TABLE order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
book_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
price_at_time DECIMAL(6, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (book_id) REFERENCES books(id)
);
β οΈ Creation Order Matters
Tables must be created in dependency order β you can't reference a table that doesn't exist yet. Create parent tables first, child tables second. The numbered comments above show a valid order.
Similarly, when dropping tables, go in reverse order β drop child tables first, parent tables last. Or use SET FOREIGN_KEY_CHECKS = 0; to temporarily disable the constraint checking (and re-enable with SET FOREIGN_KEY_CHECKS = 1; after).
Common Mistakes
Mistake 1: Putting the FK on the Wrong Side
-- β WRONG β FK on the "one" side
-- This says each customer can only have ONE order!
ALTER TABLE customers ADD COLUMN order_id INT;
-- β CORRECT β FK on the "many" side
-- This allows each customer to have MANY orders
ALTER TABLE orders ADD COLUMN customer_id INT;
The foreign key always goes on the "many" side of a 1:M relationship.
Mistake 2: Storing Comma-Separated Values
-- β WRONG β storing multiple categories in one column
INSERT INTO books (title, categories) VALUES
('Dune', 'Fiction,Sci-Fi,Classic');
-- β CORRECT β use a junction table
INSERT INTO book_categories VALUES (1, 1), (1, 2), (1, 3);
Never store multiple values in a single column. It makes querying, updating, and maintaining data integrity nearly impossible.
Mistake 3: Forgetting the UNIQUE Constraint for 1:1
-- β WRONG β this is actually 1:M without UNIQUE
CREATE TABLE profiles (
id INT PRIMARY KEY,
customer_id INT, -- missing UNIQUE!
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- β CORRECT β UNIQUE enforces 1:1
CREATE TABLE profiles (
id INT PRIMARY KEY,
customer_id INT UNIQUE NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
Mistake 4: Creating Tables in Wrong Order
-- β WRONG β books references authors, but authors doesn't exist yet
CREATE TABLE books (
id INT PRIMARY KEY,
author_id INT,
FOREIGN KEY (author_id) REFERENCES authors(id) -- ERROR!
);
CREATE TABLE authors (id INT PRIMARY KEY, name VARCHAR(100));
-- β CORRECT β create parent first
CREATE TABLE authors (id INT PRIMARY KEY, name VARCHAR(100));
CREATE TABLE books (
id INT PRIMARY KEY,
author_id INT,
FOREIGN KEY (author_id) REFERENCES authors(id)
);
Exercises
ποΈ Exercise 1: Identify the Relationship Type
For each pair, identify whether the relationship is 1:1, 1:M, or M:M:
A user and their login credentials
A teacher and their students
A blog post and its comments
A student and courses they're enrolled in
A country and its capital city
A doctor and their patients
β Solution
1:1 β Each user has exactly one set of credentials, each credential belongs to one user
M:M β One teacher can have many students, and one student can have many teachers (across different classes)
1:M β One blog post can have many comments, but each comment belongs to one post
M:M β One student enrolls in many courses, and one course has many students
1:1 β Each country has one capital, each capital belongs to one country
M:M β One doctor sees many patients, and one patient can see many doctors
ποΈ Exercise 2: Design a Schema
Design the tables (just the CREATE TABLE statements) for a music streaming service with these entities:
Artists β name, country
Albums β title, release year (one artist per album)
Songs β title, duration in seconds (each song is on one album)
Playlists β name, created date
A song can appear on many playlists, and a playlist contains many songs
β Solution
CREATE TABLE artists (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
country VARCHAR(50)
);
CREATE TABLE albums (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
artist_id INT NOT NULL,
release_year INT,
FOREIGN KEY (artist_id) REFERENCES artists(id)
);
CREATE TABLE songs (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
album_id INT NOT NULL,
duration_seconds INT,
FOREIGN KEY (album_id) REFERENCES albums(id)
);
CREATE TABLE playlists (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
created_date DATE NOT NULL
);
CREATE TABLE playlist_songs (
playlist_id INT NOT NULL,
song_id INT NOT NULL,
position INT, -- track order in the playlist
PRIMARY KEY (playlist_id, song_id),
FOREIGN KEY (playlist_id) REFERENCES playlists(id),
FOREIGN KEY (song_id) REFERENCES songs(id)
);
ποΈ Exercise 3: Spot the Problems
What's wrong with each of these designs?
Design A:
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(100),
course_name VARCHAR(100) -- "Math, Science, English"
);
Design B:
CREATE TABLE departments (
id INT PRIMARY KEY,
name VARCHAR(50),
employee_id INT,
FOREIGN KEY (employee_id) REFERENCES employees(id)
);
Design C:
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100)
);
β Solution
Design A: Storing comma-separated values in course_name. Students and courses are M:M β use a junction table (enrollments) instead.
Design B: The foreign key is on the wrong side. Departments and employees are 1:M (one department has many employees). The FK should be in the employees table as department_id, not in departments.
Design C: Table creation order is wrong. The orders table references customers, but customers hasn't been created yet. Create customers first, then orders.
π― Quick Quiz
Question 1: In a one-to-many relationship, where does the foreign key go?
Question 2: What type of relationship requires a junction table?
Question 3: How do you enforce a one-to-one relationship?
Question 4: In our bookstore schema, what type of relationship exists between books and categories?
Summary
π Key Takeaways
Splitting data into multiple tables eliminates duplication and prevents update/delete/insert anomalies
Now that you understand how tables relate to each other, it's time to make those relationships official and enforced. In the next lesson, you'll learn about foreign key constraints β the rules that MySQL enforces to keep your relationships consistent. What happens when you delete a customer who has orders? What if you try to create an order for a non-existent customer? Foreign keys answer these questions with ON DELETE and ON UPDATE rules.
π Congratulations!
You now think in relationships β one-to-one, one-to-many, many-to-many. This mental model is the foundation of every well-designed database!