SQL JOIN Types Explained: INNER, LEFT, RIGHT, FULL, CROSS (With Diagrams)

Every JOIN type answers the same question differently: "when a row on one side has no match on the other side, do I keep it, drop it, or fill it with NULL?" Rather than explain each JOIN in isolation, this guide runs the same two tables through every JOIN type — INNER, LEFT, RIGHT, FULL, CROSS, and SELF — so you can compare the exact result set side by side. Each section has a diagram and a result table you can verify by hand.

The Two Tables Used Throughout

Three customers, four orders. Notice Cara has no orders, and order 104 belongs to customer_id 9, which does not exist in the customers table — these two "orphan" rows are what make LEFT, RIGHT, and FULL JOIN behave differently from INNER JOIN.

customers
idname
1Alice
2Bob
3Cara
orders
idcustomer_idamount
1011250
102180
1032400
1049150

INNER JOIN

Returns only rows that have a match on both sides. Cara (no orders) and order 104 (no matching customer) are both dropped.

customersorders
SELECT c.name, o.id AS order_id, o.amount
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id;
nameorder_idamount
Alice101250
Alice10280
Bob103400

LEFT JOIN (LEFT OUTER JOIN)

Keeps every row from the left table (customers), even without a match. Cara now appears with NULL order fields. Order 104 is still dropped — it isn't in the left table.

customersorders
SELECT c.name, o.id AS order_id, o.amount
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;
nameorder_idamount
Alice101250
Alice10280
Bob103400
CaraNULLNULL

RIGHT JOIN (RIGHT OUTER JOIN)

The mirror image of LEFT JOIN: keeps every row from the right table (orders). Order 104 now appears with a NULL customer name. Cara is dropped — she isn't in the right table.

customersorders
SELECT c.name, o.id AS order_id, o.amount
FROM customers c
RIGHT JOIN orders o ON c.id = o.customer_id;
nameorder_idamount
Alice101250
Alice10280
Bob103400
NULL104150

Since a RIGHT JOIN can always be rewritten as a LEFT JOIN by swapping the table order (FROM orders o LEFT JOIN customers c ON ...), most style guides — including this site's — recommend sticking to LEFT JOIN everywhere for consistency and dropping RIGHT JOIN entirely.

FULL JOIN (FULL OUTER JOIN)

Keeps every row from both tables. Both Cara and order 104 appear, each with NULLs on the side that has no match.

customersorders
-- PostgreSQL, SQL Server, Oracle:
SELECT c.name, o.id AS order_id, o.amount
FROM customers c
FULL OUTER JOIN orders o ON c.id = o.customer_id;
nameorder_idamount
Alice101250
Alice10280
Bob103400
CaraNULLNULL
NULL104150

MySQL has no FULL OUTER JOIN keyword. Simulate it with a LEFT JOIN and a RIGHT JOIN combined by UNION, which de-duplicates the matched rows both sides agree on:

-- MySQL workaround:
SELECT c.name, o.id AS order_id, o.amount
FROM customers c LEFT JOIN orders o ON c.id = o.customer_id
UNION
SELECT c.name, o.id AS order_id, o.amount
FROM customers c RIGHT JOIN orders o ON c.id = o.customer_id;

CROSS JOIN

No ON condition — every row in the first table is paired with every row in the second. 3 customers × 4 orders = 12 rows, almost all of them meaningless pairings. CROSS JOIN is rarely written on purpose; it's far more often an accident from a missing or mistyped ON clause (see the FAQ below).

SELECT c.name, o.id AS order_id
FROM customers c
CROSS JOIN orders o;
-- 3 customers x 4 orders = 12 rows

SELF JOIN

Not a distinct JOIN type — it's an INNER or LEFT JOIN where a table is joined to itself, usually to compare rows within the same table. The classic example: finding each employee's manager, where both are rows in the same employees table.

SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

JOIN Types at a Glance

JOIN typeKeeps unmatched rows fromResult size (this example)Native in MySQL?
INNER JOINNeither side3 rowsYes
LEFT JOINLeft table4 rowsYes
RIGHT JOINRight table4 rowsYes
FULL OUTER JOINBoth tables5 rowsNo — use UNION workaround
CROSS JOINN/A — no matching12 rowsYes

Frequently Asked Questions

What is the difference between LEFT JOIN and RIGHT JOIN?

LEFT JOIN keeps every row from the first-listed table; RIGHT JOIN keeps every row from the second. A RIGHT JOIN can always be rewritten as a LEFT JOIN by swapping table order, which is why most codebases standardize on LEFT JOIN only.

Does MySQL support FULL OUTER JOIN?

No — simulate it with a LEFT JOIN UNION RIGHT JOIN, shown above. PostgreSQL, SQL Server, and Oracle all support it natively.

What happens if I forget the ON clause in a JOIN?

Most databases error out, or — with old comma-separated FROM syntax — silently produce a CROSS JOIN: every row paired with every row. On large tables this can generate millions of rows and hang the query.

Is there a free tool to visualize SQL JOINs from my own query?

Yes. The Dev Brains AI SQL Query Visualizer parses a pasted query and diagrams its table JOINs and clause execution order automatically, for free.

Try the Free SQL Query Visualizer

Paste your own multi-JOIN query and get an instant diagram of its table joins plus logical execution order. No signup, no cost.

Related articles