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