SQL Joins Explained
A SQL join combines rows from two or more tables based on a related column between them. Because the relational model spreads data across many small tables linked by keys, joins are how you stitch that data back together to answer real questions like "which student is enrolled in which course?"
The join type decides what happens to rows that have no match. Choosing the right type is the whole game.
The sample tables
Throughout, we use two tables:
-- student: every student
-- student_id | full_name
-- 1 | Asha
-- 2 | Rohan
-- 3 | Meera <- enrolled in nothing yet
-- enrolment: who is enrolled in what
-- enrolment_id | student_id | course
-- 101 | 1 | DBMS
-- 102 | 1 | Python
-- 103 | 2 | DBMS
-- 104 | 5 | Web Dev <- student 5 does not exist in student
Notice the deliberate gaps: Meera (3) has no enrolment, and enrolment 104 points to a missing student (5). The join type determines whether those rows appear.
INNER JOIN: only matching rows
An INNER JOIN returns rows that have a match in both tables. Rows with no match on either side are dropped.
-- PostgreSQL / MySQL compatible
SELECT s.full_name, e.course
FROM student s
INNER JOIN enrolment e
ON s.student_id = e.student_id;
-- Result:
-- Asha | DBMS
-- Asha | Python
-- Rohan | DBMS
-- (Meera is gone: no enrolment. Enrolment 104 is gone: no matching student.)
INNER JOIN is the most common join. Use it when you only want rows that exist on both sides.
LEFT JOIN: keep everything from the left
A LEFT JOIN (left outer join) returns all rows from the left table, plus matching rows from the right. Where there is no match, the right-side columns come back as NULL.
SELECT s.full_name, e.course
FROM student s
LEFT JOIN enrolment e
ON s.student_id = e.student_id;
-- Result:
-- Asha | DBMS
-- Asha | Python
-- Rohan | DBMS
-- Meera | NULL <- kept, because she is in the LEFT table
LEFT JOIN answers "show me every student, including those with no enrolments." It is the go-to for finding unmatched rows:
-- Students enrolled in NOTHING
SELECT s.full_name
FROM student s
LEFT JOIN enrolment e ON s.student_id = e.student_id
WHERE e.student_id IS NULL; -- the match failed, so right side is NULL
RIGHT JOIN: keep everything from the right
A RIGHT JOIN is the mirror image — all rows from the right table, matched where possible. In practice it is rarely used because you can always rewrite it as a LEFT JOIN by swapping the table order, which most people find easier to read.
SELECT s.full_name, e.course
FROM student s
RIGHT JOIN enrolment e
ON s.student_id = e.student_id;
-- Result includes enrolment 104 (course Web Dev) with full_name NULL,
-- because that enrolment is in the RIGHT table but has no matching student.
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesFULL OUTER JOIN: keep everything from both
A FULL OUTER JOIN returns all rows from both tables, matching where it can and filling NULL where it cannot.
SELECT s.full_name, e.course
FROM student s
FULL OUTER JOIN enrolment e
ON s.student_id = e.student_id;
-- Result includes Meera (NULL course) AND Web Dev (NULL name) AND all matches.
Vendor note: PostgreSQL supports FULL OUTER JOIN directly. MySQL does not — you emulate it by UNION-ing a LEFT JOIN and a RIGHT JOIN. This is a real portability difference.
CROSS JOIN: every combination
A CROSS JOIN pairs every row of one table with every row of the other (a Cartesian product). With 3 students and 4 enrolments you get 12 rows. It is occasionally useful (e.g. generating all date-by-category combinations) but more often the accidental result of forgetting your ON condition.
SELECT s.full_name, e.course
FROM student s
CROSS JOIN enrolment e; -- 3 x 4 = 12 rows, no condition
SELF JOIN: a table joined to itself
A SELF JOIN joins a table to itself, using aliases to tell the two copies apart. Useful for hierarchies — for example, employees and their managers in one employee table:
SELECT e.full_name AS employee, m.full_name AS manager
FROM employee e
LEFT JOIN employee m
ON e.manager_id = m.employee_id;
Common mistakes
- Forgetting the
ONcondition. A join without a join condition becomes aCROSS JOIN, exploding your row count. Always specify how the tables relate. - Using
INNER JOINwhen you neededLEFT JOIN. If rows silently disappear (e.g. students with no orders vanish from a report), you probably wanted to keep the unmatched rows. - Filtering an outer join in
WHEREinstead ofON. Putting a condition on the right table inWHEREturns aLEFT JOINback into an effectiveINNER JOIN, becauseNULLrows fail the condition. Put such conditions in theONclause. - Ambiguous column names. When both tables have a
student_id, you must qualify it (s.student_id). Always alias your tables. - Assuming MySQL supports
FULL OUTER JOIN. It does not — emulate withUNION.
FAQ
What is the difference between WHERE and ON in a join?
ON defines how rows match during the join; WHERE filters the result afterwards. For inner joins the distinction rarely matters, but for outer joins it changes which unmatched rows survive.
Can I join more than two tables?
Yes — chain JOIN clauses. Each join links one more table to the growing result. Three- and four-table joins are routine.
Which join is fastest? There is no universal answer; it depends on indexes and data sizes. Proper indexes on the join columns matter far more than the join type.
Keep learning
Joins build on keys in DBMS and feed naturally into GROUP BY & aggregate functions and subqueries.
Want to practise joins on realistic multi-table datasets? Join the waitlist for the DBMS & SQL course at Infoplanet, Jalgaon.
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesFounder, Infoplanet
Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.
Related guides
The ER Model Explained
A plain-language guide to the Entity-Relationship model — the conceptual blueprint you draw before you build any database. Covers entities, attributes, keys, relationships, cardinality, and how to convert an ER diagram into tables.
Keys in DBMS Explained
Every type of key in a relational database explained simply: super, candidate, primary, alternate, composite, foreign, and surrogate keys — with SQL you can run.
Database Normalization (1NF–BCNF)
A step-by-step walk through normalization from 1NF to BCNF, using one example table refactored stage by stage. Covers functional dependencies, the anomalies normalization prevents, and when denormalization makes sense.
