Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

What is the difference between a Common Table Expression (CTE) and a temp table? And when should I use one over the other?


WITH cte (Column1, Column2, Column3)
    SELECT Column1, Column2, Column3
    FROM SomeTable


Temp Table

SELECT Column1, Column2, Column3
INTO #tmpTable
FROM SomeTable

SELECT * FROM #tmpTable
share|improve this question
see also… – jcolebrand Feb 15 '12 at 17:02
Related on SO: – gbn Feb 16 '12 at 7:52
up vote 138 down vote accepted

This is pretty broad, but I'll give you as general an answer as I can.


  • Are unindexable (but can use existing indexes on referenced objects)
  • Cannot have constraints
  • Are essentially disposable VIEWs
  • Persist only until the next query is run
  • Can be recursive
  • Do not have dedicated stats (rely on stats on the underlying objects)

#Temp Tables...

  • Are real materialized tables that exist in tempdb
  • Can be indexed
  • Can have constraints
  • Persist for the life of the current CONNECTION
  • Can be referenced by other queries or subprocedures
  • Have dedicated stats generated by the engine

As far as when to use each, they have very different use cases. If you will have a very large result set, or need to refer to it more than once, put it in a #temp table. If it needs to be recursive, is disposable, or is just to simplify something logically, a CTE is preferred.

Also, a CTE should never be used for performance. You will almost never speed things up by using a CTE, because, again, it's just a disposable view. You can do some neat things with them but speeding up a query isn't really one of them.

share|improve this answer
A CTE without a materialize hint is a pretty cruel carrot on a stick. When the CTE is an efficient grouping as written, there should be a way to prevent the optimizer from dismantling it. Please vote on Connect item. Oracle has supported such a hint for quite awhile. It can be power equivalent to simultaneous (parallel) multiple temp table creation. – crokusek Nov 24 '13 at 8:43
CTEs can be used for performance, see Adam Mechanic's talk on Row Goals where he talks about using a CTE with TOP as a way of generating an unmaterialized temp table to give better estimates. – JoeNahmias Apr 18 at 15:50
If you watch the youtube video "watch brent tune queries"(cant post link, youtube is blocked at work) from brent ozar, There was a big decrease of logical reads when using CTE's compared to temp table. – rk13 Apr 19 at 15:40


Read more on MSDN

A CTE creates the table being used in memory, but is only valid for the specific query following it. When using recursion, this can be an effective structure, but bear in mind that it will need to be recreated every time it is needed.

You might also want to consider using a table variable. This is used as a temp table is used, but is also in-memory only, but can be used multiple times without needing to be recreated every time. Also, if you need to persist a few records now, add a few more records after the next select, add a few more records after another op, then return just those handful of records, then this is a handy in-memory structure.

Temp Table

Read more on MSDN - Scroll down about 40% of the way

A temp table is literally a table created on disk, just in a specific database that everyone knows can be deleted. It is the responsibility of a good dev to destroy those tables when they are no longer needed, but a DBA can also wipe them.

Temporary tables come in two variety: Local and global. In terms of MS Sql Server you use a #tableName designation for local, and ##tableName designation for global (note the use of a single or double # as the identifying characteristic).

Notice that with temp tables, as opposed to table variables or CTE, you can apply indexes and the like, as these are legitimately tables in the normal sense of the word.

Generally I would use temp tables for longer or larger queries, and CTEs or table variables if I had a small dataset already and wanted to just quickly script up a bit of code for something small.

share|improve this answer
The CTE is not materialised as a table in memory. It is just a way of encapsulating a query definition. In the case of the OP it will be inlined and the same as just doing SELECT Column1, Column2, Column3 FROM SomeTable – Martin Smith Feb 15 '12 at 16:55
Most of the time they do not get materialised up front, which is why this returns no rows WITH T(X) AS (SELECT NEWID())SELECT * FROM T T1 JOIN T T2 ON T1.X=T2.X, also check the execution plans. Though sometimes it is possible to hack the plan to get a spool. There is a connect item requesting a hint for this. – Martin Smith Feb 15 '12 at 17:08

A CTE may be called repeatedly within a query and is evaluated every time it is referenced - this process can be recursive. If it is just referred once then it behaves much like a sub-query, although CTEs can be parameterised.

A temporary table is physically persisted, and may be indexed. In practice the query optimiser may also persist intermediate join or sub-query results behind the scenes, such as in spool operations, so it is not strictly true that the results of CTEs are never persisted to disk.

IIRC table variables (on the other hand) are always in-memory structures.

share|improve this answer

The accepted answer here says "a CTE should never be used for performance" - but that could mislead. In the context of CTEs versus temp tables, I've just finished removing a swathe of junk from a suite of stored procs because some doofus must've thought there was little or no overhead to using temp tables. I shoved the lot into CTEs, except those which were legitimately going to be re-used throughout the process. I gained about 20% performance by all metrics. I then set about removing all the cursors which were trying to implement recursive processing. This was where I saw the greatest gain. I ended up slashing response times by a factor of ten.

CTEs and temp tables do have very different use cases. I just want to emphasise that, while not a panacea, the comprehension and correct use of CTEs can lead to some truly stellar improvements in both code quality/maintainability and speed. Since I got a handle on them, I see temp tables and cursors as the great evils of SQL processing. I can get by just fine with table variables and CTEs for almost everything now. My code is cleaner and faster.

share|improve this answer

Temp table is a real object in tempdb, but cte is only a kind of wrapper around complex query to simplify syntax of organize recursion in one step.

share|improve this answer

The primary reason to use CTEs is to access Window Functions such as row_number() and various others.

This means you can do things like get the first or last row per group VERY VERY quickly and efficiently - more efficiently than other means in most practical cases.

with reallyfastcte as (
select *, 
row_number() over (partition by groupingcolumn order by sortingcolumn) as rownum
from sometable
select *
from reallyfastcte
where rownum = 1;

You can run a similar query to the above using a correlated subquery or by using a sub-query but the CTE will be faster in almost all scenarios.

Additionally, CTEs can really help simplify your code. This can lead to performance gains because you understand the query more and can introduce more business logic to help the optimizer be more selective.

Additionally, CTEs can boost performance if you understand your business logic and know which parts of the query should be run first - typically, put your most selective queries first that lead to result sets that can use an index in their next join and add the option(force order) query hint

Finally, CTEs don't use tempdb by default so you reduce contention on that bottleneck through their use.

Temporary tables should be used if you need to query the data multiple times, or alternatively if you measure your queries and discover that by inserting to a temp table and then adding an index that your performance is improved.

share|improve this answer

There seems to be a bit of negativity here towards CTE's.

My understanding of a CTE is that it's basically a kind of adhoc view. SQL is both a declarative and a set based language. CTE's are a great way of declaring a set! Not being able to index a CTE is actually a good thing because you don't need to! It's really a kind of syntactic sugar to make the query easier to read/write. Any decent optimizer will work out the best access plan using indexes on the underlying tables. This means you could effectively speed up your CTE query by following the index advice on the underlying tables.

Also, just because you defined a set as a CTE, it doesn't mean that all rows in the set must be processed. Dependent on the query the optimizer might process "just enough" rows to satisfy the query. Maybe you only needed the first 20 or so for your screen. If you built a temp table then you really do need to read/write all those rows!

Based on this I would say that CTE's are a great feature of SQL and can be used anywhere they make the query easier to read. I would only think about a temp table for a batch process that would really need to process every single record. Even then afaik it's not really recommended because on a temp table it's far harder for the database to help you with caching and indexes. It might be better to have a permanent table with a PK field unique to your transaction.

I have to admit that my experience is mainly with DB2 so I'm assuming that CTE's work in a similar way in both products. I will happily stand corrected if CTE's are somehow inferior in SQL server. ;)

share|improve this answer

Another behavior to note is that while a CTE can be referenced multiple times in other CTEs, CTEs can only be used to return data once.


--this first call will return data
select * from my_CTE

--this second call will throw an error
select * from my_CTE

Temp Tables can be used to return data as many times as needed

--this first call will return data
select * from #my_temp_table

--this second call will also return data
select * from #my_temp_table
share|improve this answer
Thanks, but that was already listed multiple times in other answers." – Rachel Feb 15 '12 at 20:07

protected by Paul White Jan 26 '15 at 6:07

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.