JOINs
JOIN combines rows from multiple tables. INNER returns matches only; LEFT / RIGHT keep the unmatched side as NULLs; FULL keeps both. USING simplifies join conditions when the column names match.
INNER / LEFT / FULL / LATERAL
EXAMPLE
-- Sample schema
-- users(id, name)
-- orders(id, user_id, total, status)
-- 1) INNER JOIN — only rows in BOTH tables
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';
-- 2) LEFT JOIN — all users; NULLs for users without orders
SELECT u.name, COALESCE(SUM(o.total), 0) AS revenue
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid'
GROUP BY u.id, u.name;
-- 3) FULL OUTER — everything from both sides
SELECT a.name, b.name
FROM tableA a
FULL OUTER JOIN tableB b ON a.key = b.key;
-- 4) USING — shorter when column names match
SELECT u.name, o.total
FROM users u
JOIN orders o USING (user_id); -- requires user_id column in both
-- 5) Self join — hierarchy / relationships within one table
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;
-- 6) Multi-table join — chain them
SELECT u.name, p.title, c.body
FROM users u
JOIN posts p ON p.user_id = u.id
JOIN comments c ON c.post_id = p.id
WHERE u.id = 42;
-- 7) Anti-join — find users who have NEVER ordered
SELECT u.name
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.id IS NULL;
-- Or with NOT EXISTS (often faster, planner-friendly)
SELECT u.name
FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- 8) Semi-join — users who have AT LEAST ONE paid order
SELECT u.name
FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid');
-- 9) LATERAL — per-row subquery (Postgres extension)
SELECT u.name, t.recent_total
FROM users u,
LATERAL (
SELECT SUM(total) AS recent_total
FROM orders
WHERE user_id = u.id
AND created_at >= now() - interval '30 days'
) t;
-- 10) Top-N per group via LATERAL
SELECT u.name, recent.title, recent.created_at
FROM users u,
LATERAL (
SELECT title, created_at FROM posts WHERE user_id = u.id
ORDER BY created_at DESC LIMIT 3
) recent;
-- 11) JOIN performance tips
-- • Index the JOIN columns on BOTH sides
-- • EXPLAIN ANALYZE — confirm the planner picks a sensible algorithm
-- (Hash Join for big joins, Nested Loop for small, Merge Join when sorted)
-- • Avoid SELECT * on multi-table joins — duplicates large columns
-- • For OR conditions in JOIN, sometimes UNION ALL of two queries is faster
-- 12) Comma-style is the same as CROSS JOIN — careful
SELECT * FROM users u, orders o; -- CARTESIAN product if no WHERE
SELECT * FROM users u CROSS JOIN orders o; -- explicit
Why it matters
Reach for LATERAL when you need a per-row subquery (top-N per user, “most recent” per group). It composes with normal joins and is often faster than window functions.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
SELECT u.name, p.title FROM users u JOIN posts p ON p.user_id = u.id LEFT JOIN comments c ON c.post_id = p.id WHERE u.id = 1;Try it Yourself »
Discussion
Loading…