Joins
A SQL JOIN combines rows from two or more tables based on a related column between them, letting you pull connected data — like customers and their orders — into a single result.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free SQL course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll be able to combine data that's split across two tables — pairing customers with their orders, keeping unmatched rows when you need them, and understanding exactly where those NULL s come from. JOINs are the skill that turns isolated tables into real answers.
JOINs always involve two or more tables , so this lesson uses two. Every query below runs against these exact rows — keep them in mind as you read.
orders — what they bought (note customer_id links back to customers.id )
Two details that make the examples interesting: Marie Curie (id 5) has no orders at all, and order #105 points at customer_id 99 , who isn't in the customers table. Watch how each JOIN type treats these two "loose ends".
Databases deliberately split data across tables to avoid repeating themselves. A customer's name lives once in customers ; each order just stores that customer's id instead of copying the whole name. That keeps data tidy — but it means the orders table alone can't tell you who placed an order.
A JOIN stitches the tables back together for the duration of one query, matching rows by a shared value (here, the customer id). Nothing is changed on disk — the combined table exists only in the result.
Imagine two stacks of paper on your desk: a list of customers (each with an account number) and a pile of receipts (each stamped with the account number, but no name). To bill people you walk down the receipts and, for each one, find the matching customer by account number. A JOIN is that matching done automatically — the ON condition is the rule "match where the account numbers are equal".
An INNER JOIN returns only the rows that have a match in both tables. Each orders row is paired with the customers row whose id equals the order's customer_id . Rows without a partner on either side are dropped.
Two pieces of new syntax do the work. Table aliases ( FROM customers c , JOIN orders o ) give each table a short nickname so you can write c.name and o.product instead of the full table name. The ON condition states the matching rule — usually one table's primary key = another table's foreign key .
Fill in the blanks to show each buyer's city next to the product they ordered. The expected result is in the comments so you can check yourself.
A LEFT JOIN keeps every row from the left table (the one in FROM ), whether or not it finds a match on the right. Where there's no match, the right-side columns come back as NULL — SQL's marker for "no value / unknown".
This is exactly what you want when "missing" is meaningful. Marie Curie has never ordered, so an INNER JOIN would hide her — but a LEFT JOIN keeps her, with NULL where her order details would be.
A classic follow-up is to isolate just the gaps — the customers with no orders — by keeping only the rows where the matched order is NULL :
Fill in the blanks so the result lists every customer's order amount — including the customer who has never ordered (their amount should be NULL ).
A RIGHT JOIN is a LEFT JOIN seen in a mirror: it keeps every row from the right table and fills the left side with NULL when there's no match. With customers RIGHT JOIN orders , every order survives — including the orphan order #105 whose customer_id 99 matches nobody, so its name is NULL .
A FULL OUTER JOIN combines both behaviours: it keeps every row from both tables. Matched rows line up normally; unmatched rows on either side appear with NULL s filling the missing columns. It's the join you reach for when you want a complete picture and need to spot loose ends on either side.
With our data you get all five customers and all five orders: order-less Marie Curie shows up with NULL product/amount, and orphan order #105 shows up with a NULL name.
Q: What's the difference between JOIN and INNER JOIN ?
Nothing — JOIN on its own means INNER JOIN in every major database. Writing INNER explicitly just makes your intent clearer.
The one whose rows you want to keep no matter what. customers LEFT JOIN orders keeps all customers; the order is a deliberate choice, not a rule.
Yes — chain them: . Each JOIN adds one more link, each with its own ON condition. You'll meet this in later lessons.
If a customer has two orders, they appear twice — once per matching order (see Ada Lovelace above). That's correct: the result has one row per matched pair , not one per customer.
Put it all together — a brief, a blank canvas, and the expected result in the comments. Write it, then copy it into a playground to confirm. (Hint: join customers to orders, then add a WHERE .)
Practice quiz
What does an INNER JOIN return?
- All rows from both tables
- All left rows plus matches
- Only rows that have a match in both tables
- Only unmatched rows
Answer: Only rows that have a match in both tables. An INNER JOIN keeps only the rows that have a match in both tables; unmatched rows are dropped.
What is the purpose of the ON condition in a JOIN?
- It states the rule for how rows in the two tables match
- It sorts the result
- It removes duplicates
- It limits the number of rows
Answer: It states the rule for how rows in the two tables match. ON states the matching rule, typically one table's key equals another table's foreign key.
A LEFT JOIN keeps which rows?
- Only matched rows
- Every row from the right table
- No rows without a match
- Every row from the left table, plus matches from the right
Answer: Every row from the left table, plus matches from the right. A LEFT JOIN keeps every row from the left (FROM) table and attaches right-side data where it matches.
When a LEFT JOIN finds no match, what appears in the right-side columns?
- Zero
- NULL
- An empty string
- The previous row's value
Answer: NULL. Unmatched rows fill the missing columns with NULL, SQL's marker for 'no value'.
A RIGHT JOIN is equivalent to which other join with the tables swapped?
- LEFT JOIN
- INNER JOIN
- CROSS JOIN
- FULL OUTER JOIN
Answer: LEFT JOIN. Any RIGHT JOIN can be rewritten as a LEFT JOIN by swapping the order of the two tables.
What does a FULL OUTER JOIN return?
- Only matched rows
- Only left rows
- Every row from both tables, matched where possible
- Only orphan rows
Answer: Every row from both tables, matched where possible. A FULL OUTER JOIN keeps every row from both tables, with NULLs filling unmatched sides.
After a LEFT JOIN, how do you find rows with no match (the gaps)?
- WHERE o.id = NULL
- WHERE o.id IS NULL
- WHERE o.id <> NULL
- HAVING o.id IS NULL
Answer: WHERE o.id IS NULL. Keep rows where the right-side key IS NULL; you cannot test NULL with =.
In standard SQL, plain JOIN with no type word means which join?
- LEFT JOIN
- FULL OUTER JOIN
- CROSS JOIN
- INNER JOIN
Answer: INNER JOIN. JOIN on its own means INNER JOIN in every major database.
If you write FROM customers c JOIN orders o with no ON condition, what risk occurs?
- A syntax error every time
- A cross join pairing every customer with every order
- Only one row returns
- The orders table is deleted
Answer: A cross join pairing every customer with every order. Without an ON condition you can get a cross join, pairing every row with every other row.
Why might a single customer appear in multiple rows of a JOIN result?
- A bug in SQL
- DISTINCT was used
- They have multiple matching orders — one row per matched pair
- The join failed
Answer: They have multiple matching orders — one row per matched pair. A join produces one row per matched pair, so a customer with two orders appears twice.