Common Table Expressions
Common Table Expressions represent named subqueries. They can be referenced by name anywhere in aSELECT query where a table expression is allowed.
Named subqueries can be referenced by name in the scope of the current query or in the scopes of child subqueries.
Every reference to a Common Table Expression in SELECT queries is always replaced by the subquery from it’s definition if the CTE is not explicitly defined as materialized (see Materialized Common Table Expressions).
Recursion is prevented by hiding the current CTE from the identifier resolution process.
Please note that CTEs do not guarantee the same results in all places they are called because the query will be re-executed for each use case.
Syntax
Example
An example of when a subquery is re-executed:1000000
However, due to the fact that we are referring cte_numbers twice, random numbers are generated each time and, accordingly, we see different random results, 280501, 392454, 261636, 196227 and so on…
Materialized Common Table Expressions
By default, ClickHouse inlines the subquery of a CTE at each point of reference, re-executing it every time. Adding theMATERIALIZED keyword instructs ClickHouse to execute the CTE subquery exactly once, store the results in a temporary table, and serve all references from that table.
This is especially useful when the same CTE is referenced multiple times in a query (e.g., in self-joins or multiple IN subqueries), because the underlying computation only happens once.
Materialized CTEs are an experimental feature.
They require the analyzer and the setting
enable_materialized_cte to be enabled.Syntax
When to use
Materialized CTEs are most beneficial when:- The same CTE is referenced more than once in a query.
Without
MATERIALIZED, each reference re-executes the subquery independently. - The CTE contains non-deterministic functions like
generateRandom. Materializing ensures all references see the same data. - The CTE involves expensive computations (aggregations, joins, large scans) that should not be repeated.
Examples
Example 1: Self-join on a materialized CTE WithoutMATERIALIZED, both sides of the join would execute the subquery independently.
With MATERIALIZED, the table is scanned once and both join sides read from the same temporary table.
generateRandom produce different results at each reference point.
Materializing the CTE ensures consistency:
1000000.
Example 3: Chaining materialized CTEs
Materialized CTEs can reference other materialized CTEs.
ClickHouse resolves dependencies and materializes them in the correct order:
Restrictions
- Experimental setting required: The setting
enable_materialized_ctemust be enabled. - Analyzer required: Materialized CTEs only work with the analyzer enabled (
enable_analyzer = 1). - Not supported with
RECURSIVE: CombiningMATERIALIZEDandRECURSIVEkeywords is not allowed and results in anUNSUPPORTED_METHODexception. - Correlated CTEs are forbidden: A materialized CTE cannot reference columns from outer query scopes.
Common Scalar Expressions
ClickHouse allows you to declare aliases to arbitrary scalar expressions in theWITH clause.
Common scalar expressions can be referenced in any place in the query.
If a common scalar expression references something other than a constant literal, the expression may lead to the presence of free variables.
ClickHouse resolves any identifier in the closest scope possible, meaning that free variables can reference unexpected entities in case of name clashes or may lead to a correlated subquery.
It is recommended to define CSE as a lambda function (possible only with the analyzer enabled) binding all the used identifiers to achieve a more predictable behavior of expression identifiers resolution.
Syntax
Examples
Example 1: Using constant expression as “variable”extension is not bound in the gen_name lambda function body.
Although extension is defined to '.txt' as a common scalar expression in the scope of generated_names definition and usage, it is resolved into a column of the table extension_list, because it is available in the generated_names subquery.
Recursive Queries
The optionalRECURSIVE modifier allows for a WITH query to refer to its own output. Example:
Example: Sum integers from 1 through 100
Recursive CTEs rely on the query analyzer introduced in version
24.3. If you’re using version 24.3+ and encounter a (UNKNOWN_TABLE) or (UNSUPPORTED_METHOD) exception, it suggests that the analyzer is disabled on your instance, role, or profile. To activate the analyzer, enable the setting allow_experimental_analyzer or update the compatibility setting to a more recent version.
Starting from version 24.8 the analyzer has been fully promoted to production, and the setting allow_experimental_analyzer has been renamed to enable_analyzer.WITH query is always a non-recursive term, then UNION ALL, then a recursive term, where only the recursive term can contain a reference to the query’s own output. Recursive CTE query is executed as follows:
- Evaluate the non-recursive term. Place result of non-recursive term query in a temporary working table.
- As long as the working table is not empty, repeat these steps:
- Evaluate the recursive term, substituting the current contents of the working table for the recursive self-reference. Place result of recursive term query in a temporary intermediate table.
- Replace the contents of the working table with the contents of the intermediate table, then empty the intermediate table.
Search order
To create a depth-first order, we compute for each result row an array of rows that we have already visited: Example: Tree traversal depth-first orderCycle detection
First let’s create graph table:Maximum recursive CTE evaluation depth error:
Infinite queries
It is also possible to use infinite recursive CTE queries ifLIMIT is used in outer query:
Example: Infinite recursive CTE query
Trailing Comma
A comma is allowed after the last element in theWITH clause: