N017 Tier 2 · Core SQL

INNER JOIN in SQL

INNER JOIN combines rows from two tables by matching them on a condition, returning one output row for every pair of rows — one from each table — where that condition is true. Rows from either table that have no match in the other are excluded from the result.

JOIN combines rows from two tables by matching them on a shared value.

You're working on an orders report. The orders table has customer IDs but not names. The customers table has names but not order amounts. To get customer names alongside order totals, you need information from both tables in the same result row. Most business questions are like this: the data you need is spread across multiple tables, linked by shared ID columns. JOIN is the mechanism that pulls those pieces together.

The most common pattern: one table has a column that points to another table's records. The orders table stores a customer_id for each order. The customers table stores the full customer record. To get the customer's name alongside each order, you join on that shared ID.

Here's what that looks like:

SELECT c.name AS customer_name, o.id AS order_id, o.total_amount
FROM orders o
JOIN customers c ON o.customer_id = c.id

SQL scans every row in orders, finds the matching row in customers where customers.id equals that order's customer_id, and assembles one combined row. You get columns from both tables in a single result.

The ON clause defines the match condition. It almost always connects a foreign key in one table to a primary key in another. When there is no matching row on either side, that row is excluded from the result. Only rows with a match on both sides come through.

JOIN and INNER JOIN are the same thing. SQL lets you write either — the word INNER is optional and doesn't change the behaviour. You'll see both in the wild.

Table aliases keep the query readable. Both tables have an id column. Without aliases, SQL wouldn't know which one you mean. o.id refers to the orders ID; c.id refers to the customers ID. The alias goes right after the table name in FROM: FROM orders o.

You can join any tables that share a meaningful relationship:

SELECT p.name AS product_name, oi.quantity, oi.unit_price
FROM order_items oi
JOIN products p ON oi.product_id = p.id

Every order line item gets the product name attached. Line items with no matching product are dropped.

Filter the joined result with WHERE exactly as you would on a single table:

WHERE applies after the join. The two tables are combined first, then the filter removes everyone who doesn't match.

The one thing that trips people up: INNER JOIN silently drops rows with no match.

An order whose customer_id doesn't exist in the customers table disappears from the result. No error, no warning. Just fewer rows than you expected. When a result is smaller than it should be, a missing match is usually the reason. The typical fix is a LEFT JOIN, which keeps unmatched rows from the left table and fills the right-side columns with NULL. That's the next topic.

Check your understanding

An orders table has 200 rows. After INNER JOIN to customers, you get 185 rows. What does this mean?

Practice

9 INNER JOIN practice problems

These problems are part of the INNER JOIN lesson in SQLMaxx, with instant grading and a worked solution on each.

How you actually get good at SQL

Reading explains SQL. Writing it, over and over with instant feedback, is what makes you fluent.

That's the whole SQLMaxx loop: 600+ real problems, instant AI feedback, mastery you can actually see, and spaced review that won't let you forget.

A stack of SQL practice problem cards, the top card showing an employees table.
615 problems · 66 concepts

Real problems. Not toy examples.

615 hand-built problems spanning all 66 concepts, from basic SELECTs to window functions, built on real schemas and real business questions, the kind you'll actually get asked on the job. Enough reps to make SQL automatic.

A retro computer showing a SQL query marked correct with a green checkmark.
Instant AI feedback

Write a query. Know if it's right in one second.

No copying an answer and hoping it clicked. The AI grader checks your real query against real data, catches exactly what's wrong, and explains the fix in plain English, like a senior analyst reading over your shoulder on every problem.

A circular mastery progress dial filling from blue to green, the SQLMaxx diamond at its center.
Mastery tracking

Stop guessing whether you actually know it.

SQLMaxx tracks every concept and shows you what you've mastered and what's still shaky. Your skills fill in one concept at a time, so 'I think I get joins' becomes something you can prove.

A SQL query editor circled by a blue return arrow with a clock, scheduled to come back for review.
Spaced review

Learn it once. Keep it for good.

Most of what you learn this week fades by next week. So when a concept comes due for review, SQLMaxx hands you a fresh problem to solve from a blank editor, not a flashcard to re-read. A research-backed spaced-repetition algorithm (FSRS) times each return for right before you'd forget, so your SQL is still there months later, when the interview or the job actually needs it.

Practice, feedback, mastery, review. That's the loop that turns reading into real skill.

Start free

No account, no credit card. Start solving in under a minute.

FAQ

Common questions about INNER JOIN

What is an INNER JOIN in SQL?

An INNER JOIN combines rows from two tables by matching them on a condition, returning one row for every pair where the condition is true. Rows in either table with no match on the other side are left out. Use it when the columns you need are spread across two tables and you only want records that exist in both.

What is the difference between INNER JOIN and LEFT JOIN?

An INNER JOIN keeps only rows that match in both tables. A LEFT JOIN keeps every row from the left table and fills the right side with NULLs where there is no match. If you need orders that have no customer record to still appear, reach for a LEFT JOIN. If you only want orders that do have a matching customer, INNER JOIN is correct.

Does the order of the tables in an INNER JOIN matter?

For the rows returned, no. A INNER JOIN B and B INNER JOIN A produce the same set of matched rows, because the match condition is symmetric. Table order can affect column order when you select everything, and it changes how the query reads, but it does not change which rows survive the join.

Why does my INNER JOIN return fewer rows than the original table?

Because an INNER JOIN drops any row that has no match on the other side. If some orders point to a customer that does not exist in the customers table, those orders disappear from the result. When you expect every left-side row to appear whether or not it matches, use a LEFT JOIN instead.

Can I join more than two tables with INNER JOIN?

Yes. Chain another JOIN ON clause for each additional table, matching on a column that is already in the result so far. The query still returns only rows that have a match at every step, so each INNER JOIN you add can narrow the result further.