Subqueries in PostgreSQL: Scalar, IN, EXISTS & Correlated Subqueries
Learn PostgreSQL subqueries — scalar subqueries, filtering with IN and EXISTS, and correlated subqueries that reference the outer query's row.
Subqueries in PostgreSQL
A subquery is a SELECT statement nested inside another query — used when a value or filter depends on the result of a separate lookup.
Scalar Subquery — Returns a Single Value
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- employees earning above the company-wide average
Subquery with IN
SELECT name FROM employees
WHERE department_id IN (
SELECT id FROM departments WHERE name = 'Engineering'
);
Subquery with EXISTS
EXISTS only checks whether the subquery returns any rows at all — it's often faster than IN for large subquery results, since Postgres can stop as soon as it finds one match.
SELECT name FROM departments d
WHERE EXISTS (
SELECT 1 FROM employees e WHERE e.department_id = d.id
);
-- departments that have at least one employee
SELECT name FROM departments d
WHERE NOT EXISTS (
SELECT 1 FROM employees e WHERE e.department_id = d.id
);
-- departments with ZERO employees
Correlated Subquery
A subquery that references a column from the outer query — it re-runs once per outer row, unlike a plain subquery that runs once total.
SELECT name, salary, department_id
FROM employees e
WHERE salary > (
SELECT AVG(salary) FROM employees WHERE department_id = e.department_id
);
-- employees earning more than their OWN department's average
Advertisement728 x 90
Subquery in the SELECT List
SELECT
name,
(SELECT COUNT(*) FROM employees e WHERE e.department_id = d.id) AS headcount
FROM departments d;
Common Errors & Troubleshooting
ERROR: more than one row returned by a subquery used as an expression— a scalar subquery (= (SELECT ...)) must return exactly one row; if multiple rows are possible, useIN/ANY/EXISTSinstead, or add aLIMIT 1if only one is genuinely meaningful.- Correlated subquery running unexpectedly slowly — since it re-runs once per outer row, a correlated subquery over a huge table can be slow; check whether it can be rewritten as a
JOINor a window function instead (see the Window Functions lesson). NOT INsilently returning zero rows — if the subquery can produce even oneNULL,NOT INreturns no rows at all for the entire query — useNOT EXISTSinstead (covered in the Anti-Joins lesson).
Key Takeaways
- Scalar subqueries return one value and can be used anywhere a literal value would go.
EXISTS/NOT EXISTSare typically the fastest way to test "does at least one matching row exist?"- A correlated subquery references the outer query's current row, so it effectively runs once per outer row.
Advertisement320 x 100