INNER JOIN is the join type from the overview lesson's basic example, and the one meant whenever JOIN is written alone. It returns only rows where the join condition actually matches on both sides, an employee with no department_id, or a department with no employees, simply doesn't appear in the result.
Learning Objectives
- Write an INNER JOIN with an explicit, readable ON condition.
- Combine INNER JOIN with WHERE, ORDER BY, and table aliases.
- Join three tables in a single query.
employees
5 rows
departments
5 rows (one empty: Legal)
INNER JOIN
Keeps only matched pairs
Result
Legal is excluded, it has no employees
Basic INNER JOIN
Legal (department id 5) has no employees yet, so it never appears in this result, INNER JOIN only returns rows that match on both sides. Writing INNER JOIN instead of plain JOIN behaves identically, it's purely a readability choice.
SELECT employees.first_name, departments.name AS department_name FROM employees INNER JOIN departments ON employees.department_id = departments.id;
Using Table Aliases
e and d are table aliases, short stand-in names assigned right after the table name (no AS needed for a table alias, though SELECT column aliases like department_name still use AS). Once a query joins multiple tables, aliases make every column reference shorter without losing clarity, and they become close to mandatory once self-joins or multi-table joins are involved, both covered later in this module.
SELECT e.first_name, d.name AS department_name FROM employees e INNER JOIN departments d ON e.department_id = d.id;
Adding WHERE and ORDER BY
WHERE and ORDER BY work exactly as before, applied after the join has already combined rows from both tables. The query reads naturally top to bottom: pick the tables and how they relate (FROM / JOIN / ON), narrow the rows (WHERE), then order what's left (ORDER BY).
SELECT e.first_name, d.name AS department_name, e.salary FROM employees e INNER JOIN departments d ON e.department_id = d.id WHERE e.salary > 90000 ORDER BY e.salary DESC;
Joining Three Tables
Each additional JOIN ... ON adds one more table to the chain. This returns one row per employee per project they're assigned to, through the project_assignments linking table, an employee on two projects appears twice, once per project.
SELECT e.first_name, d.name AS department_name, p.name AS project_name FROM employees e INNER JOIN departments d ON e.department_id = d.id INNER JOIN project_assignments pa ON pa.employee_id = e.id INNER JOIN projects p ON pa.project_id = p.id;
Try It Yourself
Write a query that returns the first_name and department name of every employee earning more than 85,000, ordered by salary descending.
SELECT e.first_name, d.name AS department_name, e.salary FROM employees e INNER JOIN departments d ON e.department_id = d.id WHERE e.salary > 85000 ORDER BY e.salary DESC;
Frequently Asked Questions
Does the order of INNER JOIN's two tables matter?
No, for INNER JOIN specifically, employees INNER JOIN departments and departments INNER JOIN employees return the same rows (column order in the output may differ). Order matters for LEFT and RIGHT JOIN, covered next.
Can INNER JOIN's ON condition use something other than equality?
Yes, ON can use any comparison (>, <, BETWEEN), though equality on a foreign key, as in every example here, is by far the most common case. Non-equality joins are rare and situational.
Common Beginner Mistakes
Forgetting a table alias is required everywhere once one is introduced
Once a query writes FROM employees e, every reference to that table must use e, not employees, for the rest of the query. Mixing the full name and the alias in the same query is a common syntax error for beginners.
Expecting INNER JOIN to include unmatched rows
It never does, by definition. A department with zero employees, or an employee with a NULL department_id, is silently dropped from an INNER JOIN's result. If unmatched rows need to show up, that's exactly what LEFT or RIGHT JOIN is for, next.
Best Practices
- Use short, consistent table aliases (e, d, p) once a query joins more than one table.
- Write INNER JOIN explicitly rather than relying on bare JOIN, for a reader scanning the query.
- Filter with WHERE after the join, not by trying to filter inside the ON condition unless there's a specific reason to.
Interview Questions
What does INNER JOIN do with a department that has zero employees?
It excludes it entirely from the result. INNER JOIN only returns rows that have a match on both sides of the ON condition, an unmatched department row (or an unmatched employee row) simply doesn't appear.
Write a query joining employees, departments, and project_assignments to list every employee's name next to each project they're on.
SELECT e.first_name, p.name AS project_name FROM employees e INNER JOIN project_assignments pa ON pa.employee_id = e.id INNER JOIN projects p ON pa.project_id = p.id; — departments isn't needed here since the question doesn't ask for department, a correct answer only joins the tables actually required.
Summary
INNER JOIN, the default join type, returns only rows with a match in both tables, combines cleanly with WHERE and ORDER BY, and can chain across three or more tables by adding another JOIN ... ON for each one.
What's Next?
LEFT JOIN next, which keeps every row from the left table even when there's no match on the right, exactly what INNER JOIN silently drops, useful for finding Beacon Robotics' Legal department sitting empty.
Ready to Practice SQL?
Write and run real SQL queries online, right in your browser, no install or signup required. Pick a playground based on which database you want to practice on.
Both open a third-party SQL playground in a new tab. External site terms and privacy policy apply.