By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Temporary tables and table variables are SQL constructs that let you store intermediate results in memory or disk without cluttering your production database. Think of them like sticky notes for your query: you jot down a calculation, use it a few times, then toss it when you’re done.
Real-World Scenario:You’re analyzing customer churn for a SaaS company. Your raw data is in a user_activity table with 50M rows. You need to: 1. Filter active users from the last 90 days.2. Join with a subscription table to flag canceled accounts.3. Calculate churn rates by cohort.
user_activity
subscription
Without temporary tables, you’d either: - Write a monstrous nested query (hard to debug), or - Create a permanent table (clutters the database, requires cleanup).
With temporary tables, you: 1. Store the filtered users in #active_users.2. Join with subscriptions in #churn_candidates.3. Aggregate in a final query—all without touching production tables.
#active_users
#churn_candidates
#table_name
tempdb
sql CREATE TABLE #temp_users ( user_id INT, signup_date DATE, is_active BIT );
##table_name
@table_name
sql DECLARE @user_ids TABLE (id INT PRIMARY KEY);
WITH
sql WITH active_users AS ( SELECT user_id FROM users WHERE last_login > DATEADD(day, -90, GETDATE()) ) SELECT * FROM active_users;
#temp_table
##global_temp
@table_variable
#temp_tables
@table_variables
sql CREATE CLUSTERED INDEX IX_temp_users_id ON #temp_users(user_id);
OPTION (RECOMPILE)
users
subscriptions
SELECT
JOIN
GROUP BY
-- Users table CREATE TABLE users ( user_id INT PRIMARY KEY, signup_date DATE, email VARCHAR(100) ); -- Subscriptions table CREATE TABLE subscriptions ( subscription_id INT PRIMARY KEY, user_id INT, plan_name VARCHAR(50), start_date DATE, end_date DATE, -- NULL if active FOREIGN KEY (user_id) REFERENCES users(user_id) );
-- Step 1: Filter active users (logged in last 90 days) CREATE TABLE #active_users ( user_id INT PRIMARY KEY, signup_date DATE, last_login DATE ); INSERT INTO #active_users SELECT u.user_id, u.signup_date, MAX(s.start_date) AS last_login -- Approximate last activity FROM users u JOIN subscriptions s ON u.user_id = s.user_id WHERE s.start_date > DATEADD(day, -90, GETDATE()) GROUP BY u.user_id, u.signup_date;
Verification:
SELECT COUNT(*) FROM #active_users; -- Should return >0 if data exists
-- Step 2: Identify churned users (subscriptions ended in last 30 days) CREATE TABLE #churned_users ( user_id INT PRIMARY KEY, churn_date DATE ); INSERT INTO #churned_users SELECT s.user_id, s.end_date FROM subscriptions s WHERE s.end_date BETWEEN DATEADD(day, -30, GETDATE()) AND GETDATE();
SELECT * FROM #churned_users; -- Check a few rows
-- Step 3: Calculate churn rate by cohort SELECT DATEPART(year, au.signup_date) AS signup_year, DATEPART(month, au.signup_date) AS signup_month, COUNT(DISTINCT au.user_id) AS active_users, COUNT(DISTINCT cu.user_id) AS churned_users, CAST(COUNT(DISTINCT cu.user_id) AS FLOAT) / COUNT(DISTINCT au.user_id) AS churn_rate FROM #active_users au LEFT JOIN #churned_users cu ON au.user_id = cu.user_id GROUP BY DATEPART(year, au.signup_date), DATEPART(month, au.signup_date) ORDER BY signup_year, signup_month;
Expected Output:| signup_year | signup_month | active_users | churned_users | churn_rate | |-------------|--------------|--------------|---------------|------------| | 2023 | 1 | 1000 | 50 | 0.05 | | 2023 | 2 | 1200 | 60 | 0.05 |
-- Drop temporary tables (automatically dropped when session ends) DROP TABLE #active_users; DROP TABLE #churned_users;
sql CREATE CLUSTERED INDEX IX_temp_user_id ON #temp_users(user_id);
SELECT *
#
sql -- #active_users: Users with activity in the last 90 days CREATE TABLE #active_users (...);
#churned_users_2023
#temp1
sql -- SQL Server: Monitor tempdb space SELECT SUM(user_object_reserved_page_count) * 8 / 1024 AS temp_tables_mb, SUM(internal_object_reserved_page_count) * 8 / 1024 AS internal_objects_mb FROM sys.dm_db_file_space_usage WHERE database_id = DB_ID('tempdb');
sql PRINT 'Creating #active_users...'; CREATE TABLE #active_users (...);
FROM (SELECT ...) AS subquery
WHERE
ORDER BY
"You need to store 50K rows for a complex report. Which should you use?" Answer: #temp_table (better for large datasets and indexing).
Scope of temporary objects:
"A stored procedure creates a #temp_table. Can another procedure access it?" Answer: No—#temp_tables are session-scoped.
Performance implications:
"Why might a query using @table_variable run slower than one using #temp_table?" Answer: @table_variables lack statistics, leading to poor query plans.
CTE vs Temporary Table:
"You’re writing a stored procedure that processes 10K rows. The procedure runs slowly, and the query plan shows a table scan on a @table_variable. What’s the fix?" Answer:1. Replace @table_variable with #temp_table.2. Add an index to the #temp_table.3. Alternatively, use OPTION (RECOMPILE) with the @table_variable to force statistics.
Challenge:You have a sales table with columns: order_id, customer_id, order_date, amount. Write a query to: 1. Find customers who placed orders in both 2022 and 2023.2. Calculate their total spending in 2023.
sales
order_id
customer_id
order_date
amount
Constraints:- Use a temporary table to store customers who ordered in 2022.- Use a table variable to store 2023 orders for those customers.
Solution:
-- Step 1: Store 2022 customers in a temp table CREATE TABLE #customers_2022 ( customer_id INT PRIMARY KEY ); INSERT INTO #customers_2022 SELECT DISTINCT customer_id FROM sales WHERE YEAR(order_date) = 2022; -- Step 2: Use a table variable for 2023 orders (small dataset) DECLARE @customers_2023 TABLE ( customer_id INT, total_spend DECIMAL(10,2) ); INSERT INTO @customers_2023 SELECT customer_id, SUM(amount) AS total_spend FROM sales WHERE customer_id IN (SELECT customer_id FROM #customers_2022) AND YEAR(order_date) = 2023 GROUP BY customer_id; -- Step 3: Return results SELECT * FROM @customers_2023 ORDER BY total_spend DESC;
Why It Works:- #customers_2022 stores the filtered 2022 customers (large dataset → temp table).- @customers_2023 aggregates 2023 spending (small dataset → table variable).- The IN clause efficiently filters 2023 orders to only customers from 2022.
#customers_2022
@customers_2023
IN
-- Create CREATE TABLE #temp_table (id INT PRIMARY KEY, name VARCHAR(50)); -- Insert INSERT INTO #temp_table SELECT id, name FROM source_table; -- Index CREATE CLUSTERED INDEX IX_temp_id ON #temp_table(id); -- Drop (optional) DROP TABLE #temp_table;
-- Declare DECLARE @table_var TABLE (id INT PRIMARY KEY, value DECIMAL(10,2)); -- Insert INSERT INTO @table_var SELECT id, amount FROM source_table; -- Use in query SELECT * FROM @table_var WHERE id = 1;
WITH cte_name AS ( SELECT id, name FROM source_table WHERE condition ) SELECT * FROM cte_name;
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.