Skip to content
SP StackPractices
beginner

SQL Joins

Practical examples of INNER, LEFT, RIGHT, and FULL OUTER JOINs with real-world query patterns.

sqljoinsdatabasepostgresmysql
Topics: databases

Overview

SQL JOINs combine rows from two or more tables based on a related column. They are one of the most powerful and frequently misunderstood features of relational databases. This recipe demonstrates the four common JOIN types with a realistic users and orders schema.

When to Use

Use JOINs when:

  • You need data from multiple tables in a single result set
  • Normalized schemas split related data across tables (e.g., users, orders, products)
  • Reporting or analytics require aggregated data from several sources
  • You want to find orphaned or unmatched records (e.g., users without orders)

Solution

Schema

CREATE TABLE users (
    user_id INT PRIMARY KEY,
    name VARCHAR(100)
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2)
);

INSERT INTO users VALUES (1, 'Ada'), (2, 'Bob'), (3, 'Chen');
INSERT INTO orders VALUES (101, 1, 250.00), (102, 1, 75.50), (103, 2, 120.00);

INNER JOIN (matching rows only)

SELECT u.name, o.order_id, o.amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id;
nameorder_idamount
Ada101250.00
Ada10275.50
Bob103120.00

Chen has no orders, so Chen does not appear.

LEFT JOIN (all from left, matched from right)

SELECT u.name, o.order_id, o.amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;
nameorder_idamount
Ada101250.00
Ada10275.50
Bob103120.00
ChenNULLNULL

Chen appears with NULLs for missing orders.

RIGHT JOIN (all from right, matched from left)

SELECT u.name, o.order_id, o.amount
FROM users u
RIGHT JOIN orders o ON u.user_id = o.user_id;

Same result as INNER JOIN here because every order has a user. In practice, RIGHT JOIN is rare; swap table order and use LEFT JOIN instead.

FULL OUTER JOIN (all rows from both)

SELECT u.name, o.order_id, o.amount
FROM users u
FULL OUTER JOIN orders o ON u.user_id = o.user_id;

Returns all users and all orders, with NULLs where there is no match on either side. Not supported in MySQL; use UNION of LEFT and RIGHT joins as a workaround.

Explanation

  • INNER JOIN: returns only rows where the join condition matches in both tables. Use this when you only care about complete, valid pairs.
  • LEFT JOIN: returns every row from the left table, plus matching rows from the right. Use this when you want all primary records even if some lack related data.
  • RIGHT JOIN: the mirror of LEFT JOIN. Rarely used because flipping table order and using LEFT JOIN is more intuitive.
  • FULL OUTER JOIN: returns all rows from both tables. Useful for finding completely unmatched records on either side.

Variants

GoalJoin Type
Only matched pairsINNER JOIN
All users, with order totalsLEFT JOIN + GROUP BY
Users without ordersLEFT JOIN + WHERE o.user_id IS NULL
Orders without users (bad data)RIGHT JOIN or LEFT JOIN with tables swapped
All records from bothFULL OUTER JOIN (or UNION in MySQL)

Best Practices

  • Index foreign keys: the join column (orders.user_id) should have an index or foreign-key constraint. Without it, large tables perform full scans.
  • Use table aliases: users u makes queries readable and shorter.
  • Be explicit: write INNER JOIN instead of just JOIN — it communicates intent clearly.
  • Filter in the ON clause for join logic, WHERE for result filtering: ON u.id = o.user_id AND o.amount > 100 behaves differently than WHERE o.amount > 100 with LEFT JOINs.
  • Watch out for Cartesian products: forgetting the ON clause multiplies every row in table A by every row in table B.

Common Mistakes

  • Using LEFT JOIN when INNER JOIN is meant: this produces NULL rows that downstream code may not expect.
  • Joining on the wrong column: ON u.name = o.user_id compiles but gives nonsense results.
  • N+1 queries in application code: fetching a list of users, then querying orders for each user individually, is slower than a single JOIN.
  • Missing indexes: JOINs on unindexed columns are fast in development with 100 rows and catastrophic in production with millions.
  • Implicit joins: comma-separated tables in the FROM clause (FROM users, orders) are error-prone; always use explicit JOIN syntax.

Frequently Asked Questions

Q: What is the difference between JOIN and INNER JOIN? A: They are identical. JOIN is shorthand for INNER JOIN. Writing the full keyword is clearer for readers.

Q: How do I find users who have never placed an order? A: Use a LEFT JOIN and filter for NULL on the right side: SELECT u.name FROM users u LEFT JOIN orders o ON u.user_id = o.user_id WHERE o.user_id IS NULL.

Q: Can I join more than two tables? A: Yes. Chain JOINs: FROM a JOIN b ON ... JOIN c ON .... The query planner handles the order; ensure the join columns are indexed.