SQL JOIN Cheatsheet - SQL JOIN Query Reference
For developers who join tables but still get the row counts wrong. The recurring trap is that a JOIN on a one-to-many parent duplicates parent rows, inflating results unless you dedupe. By the end you can pick INNER vs LEFT by whether un-matched rows should survive, understand the duplication pitfall on one-to-many joins, express candidate-missing conditions with LEFT JOIN ... WHERE ... IS NULL, and choose EXISTS for correlated subqueries.
INNER JOIN 5
SELECT * FROM users INNER JOIN orders ON users.id = orders.user_idReturn only matched rows from both tables
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_idShort form JOIN (defaults to INNER)
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id AND o.status = 'paid'Multiple ON conditions jointly decide the match
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE o.total > 100Filter the whole result with WHERE after JOIN
SELECT u.name, o.total, p.amount FROM users u JOIN orders o ON u.id = o.user_id JOIN payments p ON o.id = p.order_idChained multi-table INNER JOIN
LEFT / RIGHT JOIN 6
SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_idAll left rows; right NULL if no match
SELECT * FROM users RIGHT JOIN orders ON users.id = orders.user_idAll right rows; left NULL if no match
SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE orders.id IS NULLAnti-join: left-only rows (users with no orders)
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'ON condition in LEFT JOIN does not drop left rows
SELECT * FROM orders o RIGHT JOIN users u ON u.id = o.user_id WHERE o.id IS NULLRIGHT JOIN anti-join (users 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.idLEFT JOIN + aggregate, count users incl. zero orders
FULL / CROSS JOIN 5
SELECT * FROM users FULL OUTER JOIN orders ON users.id = orders.user_idAll rows from both; NULL where unmatched
SELECT * FROM users u FULL JOIN orders o ON u.id = o.user_id WHERE u.id IS NULL OR o.user_id IS NULLSymmetric difference (rows unique to each side)
SELECT * FROM table1 CROSS JOIN table2Cartesian product
SELECT * FROM table1, table2Implicit CROSS JOIN (comma)
SELECT * FROM sizes CROSS JOIN colorsAll combinations (size × color)
Self & Multi-table 5
SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.idSelf-join (employee-manager)
SELECT a.name, b.name AS parent FROM categories a JOIN categories b ON a.parent_id = b.idSelf-join (category-parent)
SELECT * FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_idThree-table join
SELECT t1.name AS grandchild, t3.name AS grandparent FROM tree t1 JOIN tree t2 ON t1.parent_id = t2.id JOIN tree t3 ON t2.parent_id = t3.idMulti-level self-join (3 generations)
SELECT u.name, o.total, p.amount FROM users u JOIN orders o ON u.id = o.user_id JOIN payments p ON o.id = p.order_idThree-table (user-order-payment)
JOIN & Aggregation 5
SELECT u.name, COUNT(o.id) AS order_count FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.idOrder count per user
SELECT u.name, SUM(o.total) AS total_spent FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING SUM(o.total) > 1000HAVING filters aggregates (spent > 1000)
SELECT c.name, AVG(o.total) AS avg_order FROM customers c JOIN orders o ON c.id = o.customer_id GROUP BY c.idGroup average
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING COUNT(o.id) = 0LEFT JOIN + HAVING for zero-order users
SELECT DATE(o.created_at) AS d, COUNT(*) FROM orders o JOIN users u ON u.id = o.user_id GROUP BY DATE(o.created_at)Group by date
JOIN Performance 6
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_idShow execution plan
EXPLAIN ANALYZE SELECT * FROM users u JOIN orders o ON u.id = o.user_idRun & show timings (PostgreSQL / MySQL 8.0+)
CREATE INDEX idx_orders_user_id ON orders(user_id)Index foreign key to speed up JOIN
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_idSelect only needed columns (avoid SELECT *)
SELECT * FROM users u STRAIGHT_JOIN orders o ON u.id = o.user_idMySQL: force left table as driver
SELECT * FROM users u JOIN orders o FORCE INDEX(idx_user_id) ON u.id = o.user_idMySQL: force a specific index
USING & Natural Join 4
SELECT * FROM users JOIN orders USING (user_id)USING shorthand (same-named join column)
SELECT * FROM users u JOIN orders o USING (user_id, tenant_id)USING multiple columns (merges same-named col)
SELECT * FROM users NATURAL JOIN ordersNatural join (auto-match all same-named cols; use with care)
SELECT * FROM users u JOIN orders o ON u.id = o.user_id AND u.tenant_id = o.tenant_idMulti-column join (can use USING)
Subquery & EXISTS 5
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)Users who have orders
SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)Users with no orders
SELECT * FROM orders o WHERE o.user_id IN (SELECT id FROM users WHERE status = 'active')IN subquery filter
SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS cnt FROM users uCorrelated subquery (scalar)
SELECT * FROM orders WHERE user_id = (SELECT MAX(user_id) FROM orders)Subquery returning a single value
Tips
- A LEFT JOIN WHERE condition in ON only affects right-table matching; in WHERE it filters the whole result set.
- CROSS JOIN produces a Cartesian product — use cautiously on large data.
- Table aliases (u, o) make multi-table queries more concise and readable.
- EXISTS subqueries can outperform JOIN in some cases, especially when only existence matters.
- Index the JOIN columns, otherwise a full table scan hurts performance.
- USING(a, b) is cleaner than ON x.a = y.a AND x.b = y.b, and merges same-named columns.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Jul 21, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us