Introduction
If you’ve ever written a complex SQL query in SQL Server, chances are you've had this thought:
“Should I use a CTE here or just dump it into a temp table?”
Great question — and you’re not alone. CTEs (Common Table Expressions) and Temporary Tables both let you to break complex logic down into steps, but they serve different purposes under the hood. In this post, we'll break it all down in plain English, with examples and use cases, so you can confidently select the best option for your SQL journey.

What Is a CTE?
A CTE (Common Table Expression) is like a temporary named result set. You define it once at the top of your query, and then treat it like a virtual table.
Types of CTEs
There are two types of temporary tables:
- Simple CTE: Used to give a name to a query result.
- Multiple CTE: You can define more than one in a single query.
- Recursive CTE: A CTE that refers to itself, helpful in working with hierarchical data.
Syntax & Example
WITH TopSales AS (
SELECT TOP 5 EmployeeId, SUM(SalesAmount) AS TotalSales
FROM Sales
GROUP BY EmployeeId
ORDER BY TotalSales DESC
)
SELECT * FROM TopSales;
Think of it like giving a nickname to a subquery. It’s elegant, readable, and vanishes as soon as the query finishes.
Behind the Scenes
- CTEs are not stored in memory.
- SQL Server optimizes them as part of the main query, just like inlining code.
- You can’t index a CTE or reference it multiple times in the same query unless you write a recursive CTE.
A Quick Peek into What More CTEs Can Do
So far, we’ve looked at simple CTEs — but they can do a bit more. Two cool things to know:
1. You can use many CTEs together.
You can define many CTEs in a query by separating them with commas. It is useful when you want to create step-by-step logic in a clean, readable format.
Example:
WITH SalesSummary AS (
SELECT EmployeeId, SUM(SalesAmount) AS TotalSales
FROM Sales
GROUP BY EmployeeId
),
TopPerformers AS (
SELECT * FROM SalesSummary WHERE TotalSales > 50000
)
SELECT * FROM TopPerformers;
2. Recursive CTEs Exist Too!
CTEs can also reference themselves — this is called a recursive CTE. They're especially useful for hierarchical data like org charts, categories, or any "parent-child" structures.
Example:
WITH OrgChart AS (
SELECT EmployeeId, ManagerId FROM Employees WHERE ManagerId IS NULL
UNION ALL
SELECT e.EmployeeId, e.ManagerId
FROM Employees e
INNER JOIN OrgChart c ON e.ManagerId = c.EmployeeId
)
SELECT * FROM OrgChart;
Don't worry if it appears advanced; the idea is that this CTE will continue to repeat until the entire organizational hierarchy has been created. We'll go over this in further detail in future article!
What is a Temporary Table?
A temporary table is exactly what it sounds like: a table that exists just temporarily within your SQL Server session.
You use it when you wish to store and manipulate intermediate data without permanently storing it in your database.
Types of Temporary Tables
There are two types of temporary tables:
- Local Temporary Table (#Temp): Only visible within your session or procedure. It disappears automatically when your session ends.
- Global Temporary Table (##Temp): Visible to all sessions. These are less commonly used and should be handled with care, especially in multi-user environments.
Syntax & Example
Let’s say we want to find the top 5 employees with the highest total sales — here’s how you can do it using a temporary table:
CREATE TABLE #TopSales (
EmployeeId INT,
TotalSales MONEY
);
INSERT INTO #TopSales
SELECT TOP 5 EmployeeId, SUM(SalesAmount)
FROM Sales
GROUP BY EmployeeId
ORDER BY SUM(SalesAmount) DESC;
SELECT * FROM #TopSales;
Join the conversation! Your thoughts help the community grow.