Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
JOINs combine rows from two or more tables based on a related column. They’re one of the most-used features in SQL and one of the most commonly misunderstood — especially when NULLs or missing rows are involved.

The Tables Used in This Guide

JOIN Types at a Glance

INNER JOIN — Only Matching Rows

INNER JOIN (or just JOIN) returns rows where the join condition matches in both tables. Rows with no match in either table are excluded.
Carol has no orders — she’s excluded. The order with customer_id = NULL is also excluded because NULL doesn’t match any value. Only rows with a match on both sides appear. For a full explanation of how MySQL handles NULL comparisons, see NULL in MySQL.

LEFT JOIN — All Rows from the Left Table

LEFT JOIN returns all rows from the left table, plus any matching rows from the right table. When there’s no match, the right-side columns are NULL.
Carol appears now with NULL for amount. This is the right JOIN to use when you want all rows from the primary table, whether or not related rows exist. Finding rows with no match — filter on a NULL right-side column:

RIGHT JOIN — All Rows from the Right Table

RIGHT JOIN is the mirror of LEFT JOIN: all rows from the right table, NULLs for unmatched left-side columns. In practice, most developers rewrite RIGHT JOIN as a LEFT JOIN with the tables swapped — it’s easier to read.
Both return all orders, including the one with no customer:

CROSS JOIN — Every Combination

CROSS JOIN produces the Cartesian product: every row from the left table paired with every row from the right table. With 3 customers and 4 orders, you get 12 rows. There’s no ON condition.
Useful for generating combinations (e.g., all product-size pairings), not for querying related data.

JOIN Performance

JOINs on large tables can be slow if the join columns aren’t indexed.
Always index the foreign key column on the “many” side of a one-to-many relationship. EXPLAIN shows the join order and which indexes are used — look for type: ALL on any joined table as a sign that an index is missing.

Frequently Asked Questions

What’s the difference between JOIN and INNER JOIN?

Nothing. They’re the same. INNER JOIN is the explicit form; JOIN is shorthand. Both return only matching rows.

Why does adding a WHERE clause to a LEFT JOIN turn it into an INNER JOIN?

When you filter on a column from the right table in the WHERE clause, rows where that column is NULL (the unmatched rows) are excluded. Put the condition in the ON clause instead if you want to keep unmatched rows:

Does MySQL support FULL OUTER JOIN?

No — MySQL doesn’t have FULL OUTER JOIN. Emulate it with a LEFT JOIN and RIGHT JOIN combined with UNION:
UNION deduplicates the result. If your data can have legitimately duplicate rows and you want to preserve them, use UNION ALL — but be aware it will also include rows that appear in both halves.

Troubleshooting

See also