skip to content

What is the difference between a CTE, a derived table, and a view?

level: juniorimportance: must knowfreq 70%

answer

  1. Three ways to name a subresult
  2. The difference is how far the name reaches
  3. One block, one statement, or the whole database
  4. Only one of the three is stored in the catalog

basics

~20 s

All three name a subresult. A derived table is a subquery written inline in FROM and used once there; a CTE is named in a WITH clause and visible only to that one statement; a view is a stored schema object any statement can query.

solid answer

~60 s

All three let a query treat a subresult as if it were a table; they differ in **scope and lifetime**. A **derived table** is a subquery inside `FROM`, given a correlation name: `FROM (SELECT ...) AS d`. It exists only inside the query block that contains it, and if another part of the query needs the same result you write the subquery out again. A **CTE** is named in a `WITH` clause attached to a statement: `WITH d AS (SELECT ...) SELECT ...`. The name is available anywhere in that statement — including several times, or in a self-join — and vanishes when the statement ends. Nothing is stored. A **view** is created with `CREATE VIEW` and lives in the catalog, so any later statement, session or application can select from it. It is a schema object, so it belongs to your migrations. Rule of thumb: once in one block → derived table; more than once in one statement, or to make nesting readable → CTE; needed by more than one statement → view.

code

sql · 19 lines
sql
-- 1. Derived table: inline in FROM, correlation name required
SELECT d.dept_id, d.headcount
FROM (SELECT dept_id, COUNT(*) AS headcount
      FROM employee GROUP BY dept_id) AS d
WHERE d.headcount > 10;

-- 2. CTE: named in WITH, visible only inside this statement
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 > 10;

-- 3. View: stored in the catalog, any later statement can use it
CREATE VIEW dept_size AS
    SELECT dept_id, COUNT(*) AS headcount
    FROM employee GROUP BY dept_id;

SELECT dept_id, headcount FROM dept_size WHERE headcount > 10;

go deeper

for a junior

Be ready to define all three in one sentence each and to point at which is which in a query you are shown. Knowing that a view persists and the other two do not is the minimum expected answer.

for a middle

Explain the scoping rules precisely: which parts of a statement can see a derived table's alias, that a CTE name covers the whole statement and dies with it, and that only a view is reachable from other statements.

for a senior

Show judgment about maintenance, not just syntax: when a repeated subquery deserves promotion, when a view would become a shared dependency nobody can change, and how naming the subresult affects the next person to read the query.

for a principal

Own the convention: what your team standardises on for report queries, how many stored views the schema should carry, and how you stop one-off logic from silently becoming a schema object other teams build on.

## What all three have in common A derived table, a common table expression (CTE) and a view all do the same basic job: they give a query result a name so that a bigger query can use it as though it were a table. In all three cases the thing being named is a `SELECT` statement, and in all three cases the outer query can join to it, filter it, group it and select columns from it exactly as it would from a base table. Because they look interchangeable in a query's `FROM` clause, interviewers ask candidates to state what actually separates them. The separating axis is **scope**: how far the name reaches, and how long it lives. ## Derived table A derived table is a subquery written directly in the `FROM` clause: ```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 > 10; ``` Standard SQL requires the derived table to be given a correlation name — the alias `d` — and engines differ only in how strictly they enforce that. The alias is visible to the query block that contains the `FROM` item: that block's join conditions, `WHERE`, `GROUP BY`, `HAVING` and select list. It is not visible anywhere else. If a second `FROM` item, or a subquery elsewhere in the statement, needs the same result, you must write the subquery text out a second time. Nothing survives the statement; there is no catalog entry and no name to reuse. ## Common table expression A CTE is a subquery given a name in a `WITH` clause attached to a single statement: ```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 > 10; ``` `WITH` has been in the SQL standard since SQL:1999. Several CTEs can be listed, separated by commas, and each may reference the ones defined before it — which is why stacked CTEs read like a pipeline of named steps. An optional column list renames the output: `WITH dept_size (dept_id, headcount) AS (...)`. With `WITH RECURSIVE`, a CTE may also reference itself. The important property for this comparison is that the CTE name is visible to the **whole statement** the `WITH` is attached to. Every `FROM` in the main query can use it, including more than once and including a self-join of the CTE to itself. When the statement finishes, the name is gone: the next statement in the same transaction or session cannot select from it. Nothing is written to the catalog, and no privileges or migrations are involved — a CTE is just part of the text of one statement. ## View A view stores the query in the database under a name: ```sql CREATE VIEW dept_size AS SELECT dept_id, COUNT(*) AS headcount FROM employee GROUP BY dept_id; ``` From then on, any statement — in any session, from any application, written by anyone with access — can say `FROM dept_size`. That is the whole point of a view and the one thing neither of the other two constructs can do. The cost of that reach is that a view is a schema object: it is created and dropped with DDL, so it belongs in your migration scripts, and once other queries reference it, changing its shape becomes a coordinated change rather than an edit to one file. A view also takes no parameters; callers narrow it by adding their own predicates against it in their own `WHERE`. ## Choosing between them - Needed once, inside one query block → **derived table**. It keeps the definition next to its only use. - Needed more than once in one statement, or the nesting has become hard to follow → **CTE**. One definition, one name, referenced as often as you like. - Needed by more than one statement, report or team → **view**. One definition in the database instead of copies in several files. Naming is an underrated part of the choice. `WITH active_customer AS (...)` tells the next reader what the subresult *means*; a nested `(SELECT ...) AS t3` does not. ## What the comparison is not about Candidates often jump straight to "a CTE is materialized and a derived table is inlined." Whether an engine computes a subresult once, folds it into the surrounding query, or re-evaluates it per reference is an engine-level decision that varies by engine and by version, and it is not what the language guarantees. At the language level the guarantee is about naming and scope: a CTE gives you one definition reachable from the whole statement, a derived table gives you a definition reachable from one block, and a view gives you a definition reachable from every statement. ## Common mistakes Forgetting the alias on a derived table; expecting a CTE name to still exist in the next statement; creating a view for a subresult only one query will ever want, which adds a schema object nobody can safely delete later; and assuming that because the three read alike in `FROM`, the choice does not matter to the people who maintain the query afterwards.

  • Can a second FROM item in the same query select from a derived table defined earlier in that FROM clause?
    No. A derived table's correlation name is usable inside the query block that contains it — its join conditions, WHERE, GROUP BY and select list — but you cannot start another table reference that reads from it by name. That is exactly the limitation a CTE removes: a CTE name is available to every FROM in the statement.
  • Does a CTE name survive to the next statement in the same transaction?
    No. A CTE belongs to the single statement its WITH clause is attached to. When that statement ends the name is gone, whether or not the transaction is still open. If a second statement needs the same definition you either repeat it or promote it to a stored object such as a view.
  • Can a view's defining query itself contain a WITH clause?
    In engines that support both features this is generally allowed, and it is a normal way to keep a complex view readable: the WITH stack is part of the stored query text. Support is an engine detail, so confirm it in your engine's documentation before relying on it.

A derived table is a note scribbled in the margin of one paragraph, a CTE is a definition at the top of the page that the whole page may use, and a view is an entry in the dictionary everyone shares.

saying these in an interview costs you the question

  • Says a CTE is stored in the database like a view
  • Claims a CTE is always materialized and a derived table never is
  • Thinks a CTE name is reusable by later statements in the session
  • Forgets a derived table needs a correlation name
  • Says the three are interchangeable, so the choice does not matter

context