Why prefer a CTE over repeating a derived table when one query needs the same subresult twice?
answer
- Think about where the alias is visible
- The problem is two copies of the same text
- One name, any number of references
- Careful before you claim it runs only once
basics
~20 sA derived table's alias reaches only the block it sits in, so a second use means pasting the subquery again and keeping two copies in sync. A CTE is written once, named, and referenced as often as the statement needs.
solid answer
~50 sA derived table is scoped to the query block that contains it, so there is no name another part of the statement can reach. If the outer query and a subquery both need the same aggregate, the text has to appear twice — and two copies drift the moment somebody edits one of them and misses the other. A `WITH` clause fixes this at the language level: one definition, one name, referenced from every `FROM` in the statement, including a self-join of the CTE to itself. That also makes the query self-documenting, because the subresult gets a meaningful name such as `dept_size` instead of `t2`. What this does *not* buy you is a guarantee about evaluation. Whether the engine computes the subresult once or re-expands it per reference is an engine and version decision, not something the `WITH` keyword promises. The language-level win is single-definition maintenance and readability.
code
sql · 16 lines-- Written twice: two copies of the same definition to keep in sync
SELECT d.dept_id, d.headcount
FROM (SELECT dept_id, COUNT(*) AS headcount
FROM employee GROUP BY dept_id) AS d
WHERE d.headcount = (SELECT MAX(x.headcount)
FROM (SELECT dept_id, COUNT(*) AS headcount
FROM employee GROUP BY dept_id) AS x);
-- Written once, referenced twice
WITH dept_size AS (
SELECT dept_id, COUNT(*) AS headcount
FROM employee GROUP BY dept_id
)
SELECT dept_id, headcount
FROM dept_size
WHERE headcount = (SELECT MAX(headcount) FROM dept_size);go deeper
Recall that a WITH clause lets you name a subquery once and use that name several times in the same statement, while a subquery in FROM has to be written out again for each use.
Explain the scoping reason behind the duplication, and be precise about what WITH does and does not guarantee — single definition, yes; single evaluation, engine-dependent.
Frame it as a maintenance argument: two copies of a definition drift silently and produce disagreeing results. Show you would not claim a performance win without reading a plan on the actual engine.
Set the team norm for when a subresult earns a name, so review catches copy-pasted definitions before they diverge, and so query files do not fill with single-use CTEs that add ceremony without meaning.
## The situation Many real queries need the same intermediate result in two places: compare each row's value with the maximum of the same aggregate, join a summary back to itself for a period-over-period comparison, or filter one summary by a number computed from that same summary. The question is how you express "the same subresult, twice" in one statement. ## Why a derived table forces duplication A derived table is a `FROM` item with a correlation name, and that name is local to the query block containing it. There is no way for a different query block — a scalar subquery in `WHERE`, another `FROM` item, a `HAVING` predicate — to say "give me that thing again." The only option is to write the subquery a second time: ```sql SELECT d.dept_id, d.headcount FROM (SELECT dept_id, COUNT(*) AS headcount FROM employee GROUP BY dept_id) AS d WHERE d.headcount = (SELECT MAX(x.headcount) FROM (SELECT dept_id, COUNT(*) AS headcount FROM employee GROUP BY dept_id) AS x); ``` The two copies are now a maintenance liability. When the definition of "headcount" changes — exclude terminated employees, count only full-time staff, add a `WHERE` on a date — whoever edits the query has to remember there are two of them. Editing one and not the other does not raise an error; it silently produces a query whose inner and outer notions of the same concept disagree. That class of bug is hard to spot in review because the two blocks are far apart on screen. ## What the CTE gives you ```sql WITH dept_size AS ( SELECT dept_id, COUNT(*) AS headcount FROM employee GROUP BY dept_id ) SELECT dept_id, headcount FROM dept_size WHERE headcount = (SELECT MAX(headcount) FROM dept_size); ``` The `WITH` name is in scope for the entire statement, so both references resolve to the same text. There is exactly one place to change the definition. The name also carries meaning: a reader sees `dept_size` and knows what the subresult is without reading its body, which is not true of `AS x`. The same property enables shapes that are awkward otherwise — a self-join of the subresult, for instance: ```sql WITH monthly AS ( SELECT month_start, SUM(amount) AS total FROM sales GROUP BY month_start ) SELECT cur.month_start, cur.total - prev.total AS change FROM monthly cur JOIN monthly prev ON prev.month_start = cur.month_start - INTERVAL '1' MONTH; ``` Without the CTE you would write that aggregate out twice. ## What a CTE does not promise The most common overclaim in interviews is "a CTE is computed once, so it is faster than repeating the subquery." That is a statement about engine behaviour, and engines differ: some fold a CTE into the surrounding query like any other subquery, some evaluate it separately, and several offer hints or keywords to influence the choice — behaviour that has also changed across major versions. Nothing in the `WITH` keyword itself guarantees single evaluation. If you claim it, an interviewer will ask which engine and which version, and the honest answer is that you would check the plan. The portable, language-level claims are safe and sufficient: one definition instead of two, a name that documents intent, and the ability to reference the subresult anywhere in the statement including more than once. ## When a derived table is still the right call A `WITH` clause is not automatically better. A short subquery used exactly once, in one block, often reads better inline — the definition sits right where it is used, and the reader's eye does not have to travel to the top of the statement and back. A stack of five CTEs each used once can be harder to follow than the nested form it replaced, because the reader must hold five names in their head to understand the final `SELECT`. Reach for the CTE when the subresult is used more than once, when the nesting depth has become genuinely hard to read, or when naming the step adds real information. ## Scope reminder All of this stays inside one statement. If a *second statement* needs the same subresult, neither construct helps — the CTE name is gone once the statement ends, and the derived table never had a name to begin with. That is the case where a stored object such as a view becomes the right answer. ## Common mistakes Asserting single evaluation as a guarantee; converting every subquery to a CTE reflexively and producing a wall of single-use names; and, in the duplicated-derived-table form, editing one copy of the subquery and leaving the other behind.
- Does naming a subresult in a WITH clause guarantee the engine computes it only once?No. Whether a CTE is evaluated separately or folded into the surrounding query is an engine decision that varies by product and by version, and some engines expose keywords or hints to steer it. The language guarantee is one definition and one name, not one evaluation. If evaluation count matters for a specific query, read the plan on the engine you deploy on.
- When is a derived table still the better choice over a CTE?When the subresult is used exactly once, in one query block, and is short. Keeping it inline puts the definition where it is used, and avoids a WITH stack of single-use names that the reader must hold in their head before reaching the final SELECT. Reach for a CTE when there is a second reference or the nesting has become hard to read.
saying these in an interview costs you the question
- Claims WITH guarantees the subquery is executed only once
- Says a derived table alias can be reused elsewhere in the statement
- Converts every subquery into a CTE regardless of reuse
- Ignores that two pasted copies drift when one is edited
- Thinks a CTE can be referenced by the following statement too