Skip to main content
A common table expression (CTE) is a named subquery declared with WITH before the main SELECT. Each CTE behaves like a temporary, query-scoped data source that you can reference by name from FROM, JOIN, and UNION branches. CTEs are useful when the same intermediate result is used in more than one place, when a query reads better as a sequence of named steps, or when you want to keep a complex pre-filter out of the main SELECT. Added in 3.2.0.

Syntax

  • A single WITH keyword declares any number of comma-separated CTEs.
  • Each CTE name must be unique within the WITH block.
  • Later CTEs can reference earlier ones (forward-only chaining).
  • The main SELECT and every JOIN / UNION branch can reference any declared CTE by name in their FROM or join source.
  • EXPLAIN and EXPLAIN ANALYZE accept a leading WITH block: EXPLAIN WITH ... SELECT ....

Forward chaining

A CTE body may reference any CTE declared earlier in the same WITH block. Mutual recursion and back-references are rejected at parse time.

Using CTEs in JOIN and UNION

A CTE can appear anywhere a file source can — in the main FROM, in any JOIN, and in any UNION branch.

Fluent API

Register named subqueries on the parent query with with($name, $query). The CTE is then visible to any JOIN or UNION branch the parent composes. Inspect the registered CTEs with hasCte(), getCte(), and getCtes().
FROM-position CTE references are parser-only — the source stream of an already-constructed Query is immutable, so the fluent API uses CTEs through JOIN and UNION rather than as a FROM target. Use the FQL string form when you need a CTE in FROM.

Evaluation strategy

FiQueLa picks per-reference between two strategies, so a CTE used exactly once in a JOIN never pays an in-memory buffer: Cycles (a CTE that transitively references itself) are rejected at build time with a clear error.

Errors

The parser surfaces problems before any file is opened:
  • Duplicate CTE name in the same WITH block — ParseException.
  • WITH RECURSIVE is not supported — ParseException (the RECURSIVE keyword exists solely to raise a targeted error).
  • Reference to an unknown CTE name in FROM or JOINParseException listing every declared name in scope, so typos surface immediately.
  • DESCRIBE WITH ... — rejected (DESCRIBE expects a source, not a SELECT).

Limitations

  • WITH RECURSIVE is not supported.
  • Nested WITH blocks inside subqueries are not supported — declare every CTE at the top level.
  • CTE references inside IN (...) and EXISTS (...) are not supported (subqueries in condition operands are not yet parsed).
  • DESCRIBE cannot be combined with WITH.