In complex SQL workflows, we often need to store intermediate query results temporarily β for reuse, performance optimization, or readability. SQL offers multiple ways to handle this: temporary tables, table variables, and common table expressions (CTEs). Each serves a specific use case β knowing when to use which one is crucial for performance and scalability.
A temporary table is a physical table created in tempdb that exists only for the session (or scope) in which itβs created.
CREATE TABLE #EmployeeTemp (
EmpID INT,
EmpName VARCHAR(100),
Department VARCHAR(50)
);
INSERT INTO #EmployeeTemp VALUES (101, 'Suresh', 'QA'), (102, 'Sharmili', 'Dev');
SELECT * FROM #EmployeeTemp;
| Property | Description |
|---|---|
| Prefix | # (local) or ## (global) |
| Scope | Local to session or connection |
| Stored In | tempdb |
| Supports Indexes | Yes |
| Performance | Faster for large datasets |
| Usage | Reused across multiple queries or stored procedures |
-- Local Temporary Table
CREATE TABLE #Temp1 (id INT);
-- Global Temporary Table (accessible by other sessions)
CREATE TABLE ##TempGlobal (id INT);
A table variable is an in-memory table-like structure declared using DECLARE. They are lightweight and ideal for small datasets or procedural logic.
DECLARE @Employee TABLE (
EmpID INT,
EmpName VARCHAR(100)
);
INSERT INTO @Employee VALUES (201, 'Rahul'), (202, 'Neha');
SELECT * FROM @Employee;
| Property | Description |
|---|---|
| Scope | Batch / Stored Procedure |
| Performance | Good for small data; slower for large data |
| Transaction Logging | Minimal |
| Indexes | Only primary key or unique constraints |
| Stored In | Memory (tempdb if needed) |
| Recompilation | Does not trigger re-optimization like temp tables |
A CTE is a temporary named query result that exists only within the execution scope of a single SQL statement. CTEs improve readability and recursion, but are not stored physically.
WITH DepartmentCount AS (
SELECT Department, COUNT(*) AS EmpCount
FROM Employees
GROUP BY Department
)
SELECT *
FROM DepartmentCount
WHERE EmpCount > 5;
WITH keyword.WITH EmployeeHierarchy AS (
SELECT EmpID, ManagerID, EmpName
FROM Employees
WHERE ManagerID IS NULL
UNION ALL
SELECT e.EmpID, e.ManagerID, e.EmpName
FROM Employees e
INNER JOIN EmployeeHierarchy eh ON e.ManagerID = eh.EmpID
)
SELECT *
FROM EmployeeHierarchy;
| Feature | Temporary Table | Table Variable | CTE |
|---|---|---|---|
| Scope | Session | Batch / Procedure | Single Query |
| Stored In | tempdb | Memory / tempdb | Memory (query only) |
| Performance | Best for large datasets | Best for small datasets | Great for readable transformations |
| Indexes | Yes | Limited | No |
| Reusability | Yes (within session) | Yes (within scope) | No |
| Transactions | Fully supported | Partially supported | N/A |
| Recursive Query Support | No | No | β Yes |
| Primary Use Case | ETL, intermediate result caching | Procedural logic | Readability, recursion |
| Scenario | Best Option |
|---|---|
| Store and reuse data across multiple queries | Temporary Table |
| Small, fast in-memory dataset | Table Variable |
| Simplify long complex query or recursion | CTE |
| Need indexing and statistics | Temporary Table |
| Inside stored procedure | Table Variable |
Scenario: Fetch top-performing employees per department and reuse in multiple queries.
-- Step 1: Store result in a temporary table
SELECT * INTO #TopPerformers
FROM Employees
WHERE Rating > 4.5;
-- Step 2: Use the same temp table in next queries
SELECT Department, COUNT(*)
FROM #TopPerformers
GROUP BY Department;
-- Step 3: Clean up
DROP TABLE #TopPerformers;