What is a Common Table Expression, and how is it different from a subquery?
A CTE is a named SELECT statement introduced by a WITH clause and defined for the rest of the statement. It is not stored anywhere; it is a way of naming an intermediate result so the main query can refer to it.
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL
);
INSERT INTO orders (id, customer_id, amount, status) VALUES
(1, 1, 120.00, 'shipped'),
(2, 1, 340.50, 'pending'),
(3, 2, 75.25, 'shipped'),
(4, 2, 15.00, 'cancelled');
WITH shipped AS (
SELECT customer_id, amount
FROM orders
WHERE status = 'shipped'
),
totals AS (
SELECT customer_id, SUM(amount) AS shipped_total
FROM shipped
GROUP BY customer_id
)
SELECT customer_id, shipped_total
FROM totals
WHERE shipped_total > 100
ORDER BY customer_id;Two rows come back: customer 1 with 460.50 and customer 2 with 75.25.
The structural differences from a subquery are the name and the scope. A CTE is given a name and can be referenced by name, which means several CTEs can build on each other — here shipped feeds totals, and both are readable in the final query. A subquery is an expression nested inside a clause, so it has no name of its own and cannot be referenced twice; if you need the same intermediate result in two places you either repeat it or use a CTE.
The WITH clause can appear at the start of a SELECT, and also at the start of an INSERT, UPDATE, DELETE and MERGE, where it supplies the rows the statement operates on. It cannot appear inside a subquery in the SQL standard, although SQLite and PostgreSQL both allow a nested WITH.
Two things to know about the name. It must be given, and it must be unique within the statement — you cannot have two CTEs with the same name, and you cannot reuse a CTE name for a table in the FROM clause. And a CTE may be forward-referenced in some engines, but relying on that ordering is a portability risk; write them in dependency order.