Joins & Relationships
Objective
Write queries joining multiple tables using INNER, LEFT, RIGHT, and FULL OUTER joins, and understand when to use each.
Tools & Technologies
INNER JOINLEFT JOINRIGHT JOINFULL OUTER JOINsubquery
Key Commands
SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id=o.user_id;LEFT JOINWHERE b.id IS NULLUNION ALLArchitecture Overview
graph LR
subgraph JOIN Types
A[Table A] & B[Table B]
INNER[INNER JOIN\nOnly matching rows]
LEFT[LEFT JOIN\nAll A + matching B]
RIGHT[RIGHT JOIN\nMatching A + all B]
FULL[FULL OUTER\nAll rows both tables]
end
style INNER fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0
style LEFT fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0
Step-by-Step Process
01
INNER and LEFT JOINs
The most common join types.
-- INNER JOIN: only rows that match in both tables
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
INNER JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;
-- LEFT JOIN: all users, even those with no orders
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;
02
Anti-joins and CTEs
Find records with no match, use CTEs for readability.
-- Find users with no orders (anti-join)
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
-- CTE
WITH active_users AS (
SELECT id FROM users WHERE last_login > NOW() - INTERVAL '30 days'
)
SELECT u.name, COUNT(o.id)
FROM active_users au
JOIN users u ON u.id = au.id
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;
Challenges & Solutions
- Missing JOIN condition creates a cartesian product — every row with every row
- NULL comparisons in WHERE can exclude LEFT JOIN non-matching rows unexpectedly
Key Takeaways
- LEFT JOIN + WHERE b.id IS NULL is the anti-join pattern for 'not in' checks
- CTEs improve readability but are not always optimized as inline views