The Moment Joins Finally “Clicked” for Me
SQL Joins Explained with a Real IMDb-Style Database

I am a full-stack developer (currently working with cognizant) who is passionate about web development and creating digital products with innovative solutions.
I hold 6+ years of experience in development, understanding clients' unique needs, and delivering quality code. I've honed my coding skills over these years through both my professional exposure and self-study. Have worked with teams that have used agile development methodology.
I am someone who likes to keep acquiring new skills and capabilities. And when I am not a web developer, I enjoy reading, writing and running.
My interests include:
- Exploring new ideas
- Learning about future tech & products
- Finding user-friendly solutions to tech-related problems
- Sharing my knowledge with close acquaintances
Early in my backend career, I had a database that looked perfectly fine.
One table for shows.
One table for users.
One table for reviews.
Everything felt organized.
But the moment I tried to answer a simple question, things got messy:
“Show me every review, who wrote it, and which series it belongs to.”
Suddenly I was juggling multiple queries, stitching results in application code, and wondering why SQL felt… awkward.
Then someone showed me a single SQL query using joins.
One query.
Three tables.
Clean output.
That’s when it clicked:
Relational databases are powerful not because of tables — but because of relationships.
In this guide, we are going to explore how multi-table joins work in real systems using a TV series review platform (similar to IMDb or Rotten Tomatoes).
We’ll cover:
Normalized database design
Bridge tables and relationships
JOIN queries across tables
Aggregations for analytics
Handling missing relationships
Real-world backend insights
This is where SQL moves from CRUD operations to relational thinking.
1️⃣ Why Real Databases Use Multiple Tables
In real applications, data is rarely stored in one giant table.
Instead, we normalize data into multiple related tables.
Why?
Because duplication creates chaos.
Imagine storing this in a single table:
| series | reviewer | rating |
|---|---|---|
| Breaking Bad | Thomas | 9.5 |
| Breaking Bad | Wyatt | 9.3 |
| Breaking Bad | Pinkie | 9.7 |
Now imagine updating the show title.
You’d need to update every row.
Normalization fixes this.
Instead we store:
Series → TV shows
Reviewers → Users giving ratings
Reviews → Connection between them
This keeps data clean and relationships clear.
2️⃣ The Database Schema
Our TV review platform uses three tables.
Reviewers Table
Stores reviewer profiles.
CREATE TABLE reviewers (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL
);
Example data:
| id | first_name | last_name |
|---|---|---|
| 1 | Thomas | Stoneman |
| 2 | Wyatt | Skaggs |
Each reviewer can write many reviews.
Series Table
Stores TV show information.
CREATE TABLE series (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100),
released_year YEAR,
genre VARCHAR(100)
);
Example:
| id | title | genre |
|---|---|---|
| 1 | Archer | Animation |
| 5 | Breaking Bad | Drama |
One series can receive many reviews.
Reviews Table (Bridge Table)
This is where the relationships live.
CREATE TABLE reviews (
id INT PRIMARY KEY AUTO_INCREMENT,
rating DECIMAL(2,1),
series_id INT,
reviewer_id INT,
FOREIGN KEY (series_id) REFERENCES series(id),
FOREIGN KEY (reviewer_id) REFERENCES reviewers(id)
);
The relationship looks like this:
Reviewers (1) → (M) Reviews (M) → (1) Series
Meaning:
One reviewer can write many reviews
One series can receive many reviews
This table acts as a bridge connecting the two entities.
3️⃣ Your First Real Join Query
Let’s answer a simple question:
Show each series with its ratings.
SELECT
title, rating
FROM series
JOIN reviews
ON series.id = reviews.series_id;
Example output:
| title | rating |
|---|---|
| Archer | 8.0 |
| Archer | 7.5 |
| Breaking Bad | 9.5 |
Each row represents one review of a series.
Joins allow SQL to combine data from multiple tables.
4️⃣ Calculating Average Rating Per Series
Now let’s build something closer to a real product feature.
A movie platform needs average ratings.
SELECT
title,
ROUND(AVG(rating), 2) AS avg_rating
FROM series
JOIN reviews
ON series.id = reviews.series_id
GROUP BY title
ORDER BY avg_rating;
Example result:
| title | avg_rating |
|---|---|
| Breaking Bad | 9.36 |
| Fargo | 9.40 |
This powers features like:
ranking pages
recommendation algorithms
popularity charts
5️⃣ Finding Series With No Reviews
This is a real production scenario.
A product manager asks:
“Which shows have no reviews yet?”
We use a LEFT JOIN.
SELECT
title AS unreviewed_series
FROM series
LEFT JOIN reviews
ON series.id = reviews.series_id
WHERE rating IS NULL;
Why this works:
LEFT JOIN keeps all series
Missing matches become NULL
So if a series has no reviews, it appears here.
RIGHT JOIN Alternative
Another version:
SELECT
title AS unreviewed_series
FROM reviews
RIGHT JOIN series
ON series.id = reviews.series_id
WHERE rating IS NULL;
Both return the same result.
But most engineers prefer LEFT JOIN for readability.
6️⃣ Analytics Example: Average Rating by Genre
Businesses love analytics.
Let’s analyze genre performance.
SELECT
genre,
ROUND(AVG(rating), 2) AS avg_rating
FROM series
JOIN reviews
ON series.id = reviews.series_id
GROUP BY genre;
Example:
| genre | avg_rating |
|---|---|
| Drama | 8.7 |
| Comedy | 7.9 |
This helps platforms:
recommend content
decide which genres to invest in
analyze viewer engagement
7️⃣ Reviewer Activity Statistics
Another real-world need:
Which reviewers are most active?
SELECT
first_name,
last_name,
COUNT(rating) AS count,
IFNULL(MIN(rating), 0) AS min,
IFNULL(MAX(rating), 0) AS max,
ROUND(IFNULL(AVG(rating), 0), 2) AS average,
CASE
WHEN COUNT(rating) >= 10 THEN 'POWERUSER'
WHEN COUNT(rating) > 0 THEN 'ACTIVE'
ELSE 'INACTIVE'
END AS status
FROM reviewers
LEFT JOIN reviews
ON reviewers.id = reviews.reviewer_id
GROUP BY first_name , last_name;
Example output:
| reviewer | reviews | status |
|---|---|---|
| Thomas | 12 | POWERUSER |
| Pinkie | 5 | ACTIVE |
| Marlon | 0 | INACTIVE |
This logic powers:
gamification systems
reviewer badges
moderation priority lists
8️⃣ Joining Three Tables
Now we combine everything.
Question:
Who reviewed which series?
SELECT
title,
rating,
CONCAT(first_name, ' ', last_name) AS reviewer
FROM reviews
INNER JOIN series
ON reviews.series_id = series.id
INNER JOIN reviewers
ON reviews.reviewer_id = reviewers.id;
Example result:
| title | rating | reviewer |
|---|---|---|
| Archer | 8.0 | Thomas Stoneman |
| Archer | 7.5 | Wyatt Skaggs |
Three tables.
One query.
That’s relational power.
9️⃣ Join Order Doesn’t Matter
Interestingly, these queries produce the same result:
reviews → series → reviewers
series → reviews → reviewers
reviewers → reviews → series
Why?
Because joins depend on relationships, not table order.
The database optimizer figures out the best execution path.
🛠 DevOps / Production Perspective
In real systems:
Join queries power:
recommendation engines
analytics dashboards
reporting pipelines
user activity tracking
But joins also introduce challenges:
missing indexes → slow queries
large tables → heavy CPU usage
analytics queries → read replica usage
High-scale systems often:
index foreign keys
cache aggregated results
precompute analytics tables
🧠 Mental Model to Lock In
Tables store entities.
Relationships connect them.
Joins allow SQL to reconstruct the real-world relationships between data.
This is the heart of relational databases.
Once joins make sense, SQL stops feeling like syntax…
and starts feeling like data modeling.
The Bridge Back
Remember that moment where joining tables felt complicated?
Eventually it becomes second nature.
You stop thinking:
“Which table has the data?”
And start thinking:
“How are these entities related?”
That shift — from tables to relationships — is when SQL truly clicks.



