Note A scalar subquery must return exactly one row and one column. If it returns more than one row, the query fails. If it returns zero rows, the result is NULL.
scalar subquerysingle value subquerysubquery in select
Subquery in WHERE with IN
Syntax
WHEREcolumnIN(SELECTcolumnFROM...)
Example
SELECT first_name, email
FROM users
WHERE id IN(SELECT user_id
FROM orders
WHERE total_amount >500);
Output
-- Users who have placed at least one order over 500
Note For large datasets, EXISTS often performs better than IN with a subquery because EXISTS short-circuits on the first match. IN materializes the entire subquery result first.
SELECT columns
FROM table1 t1
WHEREcolumnop(SELECTaggregate(col)FROM table2 t2
WHERE t2.ref= t1.id);
Example
SELECT product_name, price, category_id
FROM products p
WHERE price >(SELECTAVG(price)FROM products
WHERE category_id = p.category_id);
Output
-- Products priced above their own category's average
Note A correlated subquery references the outer query and executes once per outer row. This can be slow on large tables. Consider rewriting as a JOIN with a derived table for better performance.
SELECT
top_customers.first_name,
top_customers.total_spentFROM(SELECT u.first_name,SUM(o.total_amount)AS total_spent
FROM users u
JOIN orders o ON o.user_id= u.idGROUPBY u.id, u.first_nameHAVINGSUM(o.total_amount)>1000)AS top_customers
ORDERBY top_customers.total_spentDESC;
Output
-- first_name | total_spent
-- Alice | 4520.00
-- Bob | 2310.50
Note Derived tables (subqueries in FROM) must have an alias. They are computed once and treated like a temporary table. CTEs (WITH clause) are generally more readable for the same purpose.
subquery in fromderived tableinline viewtemporary table subquery
EXISTS Subquery
Syntax
WHEREEXISTS(SELECT1FROMtableWHERE condition)
Example
SELECT c.category_nameFROM categories c
WHERENOTEXISTS(SELECT1FROM products p
WHERE p.category_id= c.id);
Output
-- Categories that have no products at all
Note NOT EXISTS is the safest way to find missing related rows. Unlike NOT IN, it handles NULLs correctly and will not produce unexpected empty results.
SELECT first_name, salary
FROM employees
WHERE salary >ALL(SELECT salary
FROM employees
WHERE department_id =3);
Output
-- Employees earning more than everyone in department 3
Note ANY means 'at least one' - the condition must hold for at least one row from the subquery. ALL means 'every' - it must hold for all rows. An empty subquery makes ALL true and ANY false.
SELECT columns
FROM table1 t1
CROSSJOINLATERAL(SELECT...FROM table2 WHERE ref = t1.idLIMIT n
)AS sub;
Example
SELECT u.first_name, recent.order_id, recent.total_amountFROM users u
CROSSJOINLATERAL(SELECT order_id, total_amount
FROM orders
WHERE user_id = u.idORDERBY order_date DESCLIMIT3)AS recent;
Output
-- Each user paired with their 3 most recent orders
Note LATERAL lets a subquery reference columns from preceding tables in the FROM clause. It is the SQL equivalent of a for-each loop. PostgreSQL supports LATERAL; MySQL 8+ supports LATERAL derived tables.
lateral jointop n per groupfor each row subquerylateral subquery
Subquery in INSERT
Syntax
INSERTINTOtarget(columns)SELECT columns FROM source WHERE condition;
Example
INSERTINTOarchived_orders(order_id, user_id, total_amount, order_date)SELECT order_id, user_id, total_amount, order_date
FROM orders
WHERE order_date <'2024-01-01';
Output
-- Copies old orders into an archive table
Note The SELECT column count and types must match the INSERT column list. This is an efficient way to bulk-copy data between tables without round-tripping through the application.
insert from selectcopy data between tablesbulk insert subquery