A Common Table Expression (CTE) defines a temporary, named result set that you can reference within a single SQL Server statement.
CTEs clarify complex queries by isolating subqueries, enabling recursion, and improving readability without creating permanent tables.
Start with WITH, name the CTE, supply an AS clause containing a SELECT, then query the CTE in the outer statement.
WITH RecentOrders AS (
SELECT id, customer_id, order_date, total_amount
FROM Orders
WHERE order_date >= DATEADD(day,-7,GETDATE())
)
SELECT *
FROM RecentOrders;
Choose a CTE when the interim result is reused, when recursion is needed, or when breaking a multi-step calculation into readable blocks.
Anchor the first SELECT, UNION ALL to the recursive member, and reference the CTE inside itself.
WITH StockLevels AS (
SELECT id, name, stock, 1 AS lvl
FROM Products
WHERE id = 42
UNION ALL
SELECT p.id, p.name, p.stock, lvl+1
FROM Products p
JOIN StockLevels s ON p.id = s.id
WHERE p.stock < 10
)
SELECT * FROM StockLevels;
Yes.Chain them with commas, then query any defined name in the final SELECT.
WITH BigSpenders AS (
SELECT customer_id, SUM(total_amount) AS spend
FROM Orders
GROUP BY customer_id
HAVING SUM(total_amount) > 1000
),
LastContact AS (
SELECT id, MAX(created_at) AS last_seen
FROM Customers
GROUP BY id
)
SELECT c.name, b.spend, l.last_seen
FROM Customers c
JOIN BigSpenders b ON c.id=b.customer_id
JOIN LastContact l ON c.id=l.id;
Keep each CTE focused, index the underlying tables, avoid over-nesting, and always examine execution plans with SET STATISTICS.
.
SQL Server builds the CTE result in memory or spills to tempdb if needed, similar to derived tables. It is not persisted after the batch ends.
Performance is usually the same as an equivalent subquery. The gain is readability; check execution plans to confirm cost.
Yes. Use UPDATE cte_alias SET ... FROM cte_name, but ensure the CTE selects from the target table directly.