LEFT JOIN vs RIGHT JOIN in SQL: What's the Difference?
Outer joins include all rows from one table even when there's no match in the other table. Understanding LEFT and RIGHT joins is essential for complete data analysis.
Quick answer: LEFT JOIN keeps every row from the left (first) table and fills in NULLs where the right table has no match; RIGHT JOIN keeps every row from the right (second) table instead. They are mirror images โ every RIGHT JOIN can be rewritten as a LEFT JOIN by swapping the table order, which is why most SQL code uses LEFT JOIN.
What Is the Difference Between LEFT JOIN and RIGHT JOIN?
The only difference is which table's rows are guaranteed to survive. LEFT JOIN preserves all rows from the table written before the JOIN keyword; RIGHT JOIN preserves all rows from the table written after it. Matching logic, NULL filling, and performance are otherwise identical โ the choice is purely about which side you want kept in full.
| LEFT JOIN | RIGHT JOIN | |
|---|---|---|
| Keeps all rows from | Left (first) table | Right (second) table |
| NULLs appear in | Right table's columns | Left table's columns |
| Full name | LEFT OUTER JOIN | RIGHT OUTER JOIN |
| Usage in practice | Very common | Rare (usually rewritten as LEFT) |
Need all rows from both sides? That's a FULL OUTER JOIN. Only matching rows? That's an INNER JOIN.
LEFT JOIN (LEFT OUTER JOIN)
Returns all rows from the left table plus matching rows from the right table. Non-matching rows show NULL for right table columns.
-- Get ALL customers, including those without orders
SELECT
customers.name,
customers.email,
orders.order_id,
orders.total
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id;
Result includes every customer. Customers without orders will have NULL in the order_id and total columns.
RIGHT JOIN (RIGHT OUTER JOIN)
Returns all rows from the right table plus matching rows from the left table. Less commonly used than LEFT JOIN.
-- Get ALL orders, including orphaned orders without customer info
SELECT
customers.name,
orders.order_id,
orders.total
FROM customers
RIGHT JOIN orders ON customers.customer_id = orders.customer_id;
Can Every RIGHT JOIN Be Rewritten as a LEFT JOIN?
Yes. Swapping the order of the two tables and changing RIGHT to LEFT produces an identical result set โ same rows, same NULL pattern. The join condition doesn't even need to change, since a = b equals b = a. That symmetry is why some teams ban RIGHT JOIN entirely: anything it does, a reordered LEFT JOIN does more readably.
-- RIGHT JOIN version: keep all orders
SELECT * FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;
-- Equivalent LEFT JOIN: swap the tables, keep all orders
SELECT * FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id;
-- Both return every order, with NULL customer columns
-- for orders that have no matching customer.
Why Does LEFT JOIN Dominate in Practice?
Because queries read top-to-bottom, left-to-right: you start FROM your main table and progressively attach lookups to it. LEFT JOIN keeps that mental model โ "my base table, plus whatever matches." RIGHT JOIN forces readers to reason backwards from the end of the clause, and it composes badly when chaining three or more joins, so style guides and ORMs overwhelmingly emit LEFT JOIN.
Finding Non-Matching Records
Use LEFT JOIN with a NULL check to find records that don't have matches:
Customers with No Orders
SELECT
customers.customer_id,
customers.name,
customers.email,
customers.signup_date
FROM customers
LEFT JOIN orders ON customers.customer_id = orders.customer_id
WHERE orders.order_id IS NULL;
Products Never Sold
SELECT
products.product_id,
products.product_name,
products.price
FROM products
LEFT JOIN order_items ON products.product_id = order_items.product_id
WHERE order_items.order_id IS NULL;
Practical Use Cases
Customer Engagement Report
SELECT
c.customer_id,
c.name,
c.signup_date,
COUNT(o.order_id) as order_count,
COALESCE(SUM(o.total), 0) as lifetime_value,
CASE
WHEN COUNT(o.order_id) = 0 THEN 'Never Purchased'
WHEN COUNT(o.order_id) < 3 THEN 'Low Engagement'
ELSE 'Active Customer'
END as customer_status
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name, c.signup_date
ORDER BY order_count DESC;
Email List with Purchase History
SELECT
u.email,
u.first_name,
u.last_name,
MAX(o.order_date) as last_purchase_date,
DATEDIFF(CURDATE(), MAX(o.order_date)) as days_since_purchase
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE u.email_opt_in = true
GROUP BY u.email, u.first_name, u.last_name;
Complete Inventory Report
SELECT
p.product_id,
p.product_name,
p.stock_quantity,
COALESCE(SUM(oi.quantity), 0) as total_sold,
COALESCE(SUM(oi.quantity * oi.price), 0) as revenue
FROM products p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.product_name, p.stock_quantity
ORDER BY revenue DESC;
Pro Tip: Always use COALESCE() or IS NULL checks with outer joins to handle NULL values properly. Without these, your calculations and filters may produce unexpected results.
โ Back to SQL Tips