Common Table Expressions (CTEs) With WITH
Use a WITH clause to compute total spend per customer as a named, reusable result set, then filter and join against it.
Overview
A Common Table Expression, defined with WITH ... AS (...), lets you give a
name to an intermediate query result and then reference that name later in
the same statement, as if it were a temporary table. This makes multi-step
logic readable: instead of nesting subqueries inside subqueries, you build
up your answer in named stages.
A typical use case is computing a per-customer metric, like total money spent: that requires a join and an aggregation, and then using that computed metric in a further filter or join without repeating the aggregation logic.
The Query
Step-by-Step Breakdown
WITH customer_spend AS (...): defines a named result set. Inside it,ordersis joined toproductsso we can multiplyquantity * priceper line item, then grouped bycustomer_idto get one total per customer.- Once defined,
customer_spendbehaves like any other table for the rest of the statement: you canSELECTfrom it,JOINit, or filter it. SELECT c.name, cs.total_spend FROM customer_spend cs JOIN customers c ON c.id = cs.customer_id, the outer query joins the pre-computed spend totals back tocustomerspurely to get a readable name.ORDER BY cs.total_spend DESC: highest spenders first.
The CTE is evaluated once and referenced by name, which keeps the aggregation logic separate from (and easier to read than) the final join and sort.
Variations
Chain a second CTE that filters down to big spenders only:
Each CTE can reference the ones defined before it, separated by commas after
the first WITH. Here, big_spenders reads from customer_spend the same
way the final SELECT would read from a real table.
CTEs aren't automatically materialized
In DuckDB and most modern engines, a CTE isn't necessarily computed once and cached: it may be inlined into the query plan like a view, and re-evaluated if referenced multiple times. Use a CTE for readability and structure; don't rely on it for performance the way you would an actual temp table unless your database specifically documents materialization behavior.
Common Mistakes
- Thinking a CTE persists after the statement. It only exists for the
duration of the single query it's attached to: you can't reference
customer_spendin a later, separate query. - Forgetting the comma between multiple CTEs.
WITH a AS (...) b AS (...)is invalid; it must beWITH a AS (...), b AS (...). - Using a CTE where a simple subquery would be clearer. For a single, small filter, an inline subquery may be easier to read than a named CTE, reach for CTEs when you have multiple steps or want to reuse a result more than once in the same query.
- Assuming CTEs can be referenced recursively without extra syntax.
Self-referencing (recursive) CTEs need an explicit
WITH RECURSIVEdeclaration; a plainWITHCTE can only reference CTEs defined earlier in the same clause, not itself.
Cite this resource
SQLSimplified. "Common Table Expressions (CTEs) With WITH Example". Available at: https://sqlsimplified.online/examples/cte