Common Table Expressions (WITH / CTE)

A Common Table Expression (CTE) is a temporary result set defined within a single SQL query, and its scope is limited to the query statement that defines it. The main advantages of CTEs are improved SQL readability and maintainability, as well as the ability to reference the same subquery multiple times within the same query.

Syntax

WITH <cte_name1> AS (SELECT ...), [<cte_name2> AS (SELECT ...)], ... SELECT ... FROM <cte_name1> [JOIN <cte_name2> ...]

Parameter Description:

  • <cte_name>: The name of the common table expression, which can be directly referenced in subsequent queries.
  • CTEs can reference each other, but can only reference CTEs defined before them.
  • Multiple CTEs can be defined in a single query, separated by commas.

Usage Examples

Example 1: Single CTE — Filter High-Salary Employees

Query employees with a salary higher than 8000, ordered by salary in descending order.

WITH high_salary AS ( SELECT id, name, dept, salary FROM doc_test.employees WHERE salary > 8000 ) SELECT name, dept, salary FROM high_salary ORDER BY salary DESC;

Result:

+-------+-------------+----------+ | name | dept | salary | +-------+-------------+----------+ | Alice | Engineering | 12000.00 | | Bob | Engineering | 9500.00 | | Carol | Marketing | 8500.00 | +-------+-------------+----------+

Example 2: Multiple CTEs — Department Average Salary vs. Employee Comparison

First calculate the average salary for each department, then find employees whose salary is higher than the department average.

WITH dept_avg AS ( SELECT dept, AVG(salary) AS avg_salary FROM doc_test.employees GROUP BY dept ), above_avg AS ( SELECT e.name, e.dept, e.salary, d.avg_salary FROM doc_test.employees e JOIN dept_avg d ON e.dept = d.dept WHERE e.salary > d.avg_salary ) SELECT name, dept, salary, ROUND(avg_salary, 2) AS dept_avg FROM above_avg ORDER BY dept, salary DESC;

Result:

+-------+-------------+----------+----------+ | name | dept | salary | dept_avg | +-------+-------------+----------+----------+ | Alice | Engineering | 12000.00| 9500.00 | | Carol | Marketing | 8500.00 | 7500.00 | +-------+-------------+----------+----------+

Example 3: CTE with Aggregation — Active Employee Statistics by Department

Count active employees and calculate the average salary for each department.

WITH active_emp AS ( SELECT dept, salary FROM doc_test.employees WHERE is_active = TRUE ) SELECT dept, COUNT(*) AS headcount, ROUND(AVG(salary), 2) AS avg_salary, MAX(salary) AS max_salary FROM active_emp GROUP BY dept ORDER BY headcount DESC;

Result:

+-------------+-----------+------------+------------+ | dept | headcount | avg_salary | max_salary | +-------------+-----------+------------+------------+ | Engineering | 2 | 10750.00 | 12000.00 | | Marketing | 2 | 7500.00 | 8500.00 | | HR | 1 | 6000.00 | 6000.00 | +-------------+-----------+------------+------------+

Example 4: CTE with JOIN — Order Amount Summary

Summarize the total order amount for each customer and filter for customers with a total amount exceeding 500.

WITH order_summary AS ( SELECT customer_id, COUNT(*) AS order_count, SUM(amount) AS total_amount FROM doc_test.orders GROUP BY customer_id ) SELECT customer_id, order_count, total_amount FROM order_summary WHERE total_amount > 500 ORDER BY total_amount DESC;

Notes

  • The scope of a CTE is limited to the single SQL statement immediately following it and cannot be referenced across statements.
  • A later-defined CTE can reference previously defined CTEs, but cannot forward-reference.
  • Lakehouse does not support recursive CTEs (WITH RECURSIVE).
  • CTEs are not materialized (not written to disk); each reference triggers a recomputation. If you need to reuse large computation results, consider using temporary tables or dynamic tables.