Joins and Subqueries
Combine tables and nest queries. Results appear in the same fence: same-line -- comments when short, multiline -- blocks below the sample when not.
Search across all documentation pages
Combine tables and nest queries. Results appear in the same fence: same-line -- comments when short, multiline -- blocks below the sample when not.
Rows with matches on both sides.
SELECT u.email, o.id
FROM users u
JOIN orders o ON o.user_id = u.id;
-- only users who have ordersKeep left rows when right is missing.
SELECT u.id, o.id AS order_id
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;
-- order_id NULL if no orderShorthand when join column names match.
SELECT *
FROM orders o
JOIN order_items i USING (order_id);
-- join on order_idRight side can reference left columns (per-row).
SELECT u.id, r.score
FROM users u
JOIN LATERAL (
SELECT score FROM scores s
WHERE s.user_id = u.id
ORDER BY score DESC
LIMIT 1
) r ON true;
-- top score per userSubquery references outer row.
SELECT u.id
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.total > 100
);
-- users with a large orderSubquery returns one value.
SELECT name,
(SELECT count(*) FROM orders o WHERE o.user_id = u.id) AS n
FROM users u;
-- n: order countKeep unmatched rows from both sides.
SELECT *
FROM a
FULL OUTER JOIN b ON a.id = b.a_id;
-- nulls on either side when no matchCartesian product - use deliberately.
SELECT c.code, d.day
FROM currencies c
CROSS JOIN days d;
-- every currency × dayRows with no match.
SELECT u.*
FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- users without ordersExistence without multiplying rows.
SELECT u.*
FROM users u
WHERE EXISTS (SELECT 1 FROM sessions s WHERE s.user_id = u.id);
-- users with any sessionJoin a table to itself.
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;
-- manager name or nullChain joins left to right; parens rarely needed.
SELECT o.id, u.email, p.sku
FROM orders o
JOIN users u ON u.id = o.user_id
JOIN products p ON p.id = o.product_id;ON filters before outer-row preservation; WHERE filters after.
SELECT u.id, o.id
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'open';
-- open filter in ON keeps users without open ordersDerived table must be aliased.
SELECT d.user_id, d.n
FROM (
SELECT user_id, count(*) AS n FROM orders GROUP BY user_id
) d
WHERE d.n > 5;
-- users with >5 ordersStack result sets; ALL keeps duplicates.
SELECT id FROM a
UNION ALL
SELECT id FROM b;
-- all ids from bothStack versions: PostgreSQL 18.4 (stable 18, maintenance 17) · pgvector 0.8+
Reviewed by Chris St. John·Last updated Jul 19, 2026