190

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

CTE

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

SELECT * FROM cte

Temp Table

SELECT Column1, Column2, Column3
INTO #tmpTable
FROM SomeTable

SELECT * FROM #tmpTable
2

7 Answers 7

221

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

CTEs...

  • 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.

4
  • 5
    speeding up a big MERGE using CTE is a thing
    – AgentFire
    May 6, 2018 at 18:48
  • 5
    Speeding up many queries using CTE's is also a thing because with CTEs you can add your own business knowledge to outperform the query optimiser. For example, you can have part 1 of your CTE select from tables where you know that the resulting rows will be very small. Inside the same query, you can join this tiny resultset to some larger resultset and completely bypass issues caused by stale statistics etc. To do this, you need to add query hints to force the order. It works, it improves performance. May 19, 2018 at 13:28
  • 3
    "never be used for performance" is a broad and somewhat subjective statement, although I understand your point. Albeit, in addition to the other comments, another potential performance gain from using a CTE can occur when switching to a recursive CTE from another form of recursion such as recursive procedure calls or a cursor.
    – J.D.
    Mar 12, 2019 at 3:28
  • what does 'next query' mean in 'Persist only until the next query is run'?
    – AminM
    Aug 23, 2023 at 18:14
32

EDIT:

Please see Martin's comments below:

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. 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


Original answer

CTE

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.

You might also want to consider using a table variable. This is used as a temp table is used and can be used multiple times without needing to be re-materialized for each join. 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 can be a handy structure, as it doesn't need to be dropped after execution. Mostly just syntactic sugar. However, if you keep the row-count low, it never materializes to disk. See What's the difference between a temp table and table variable in SQL Server? for more details.

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. Experience and the advice of others indicates that you should use CTEs where you have a small number of rows being returned from it. If you have a large number, you would probably benefit from the ability to index on the temp table.

2
  • 12
    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 Feb 15, 2012 at 16:55
  • 5
    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. Feb 15, 2012 at 17:08
21

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.

5
  • 1
    Now, let's be fair - cursors are the great evil; temp tables are at worst a lesser evil. :-) It's really unfair to put them at the same level, as you saw yourself.
    – RDFozz
    Jan 22, 2018 at 20:50
  • 1
    @RDFozz right, hell has 9 circles as we all know. Lets put temp tables at 2nd and cursors at ... 7th? ;) Jan 22, 2018 at 21:52
  • 4
    You know what the 'great evil' is in programming? When people say that a particular technique is evil. There is a place for cursors. They can outperform other techniques in certain scenarios. There's no evil here - you need to learn to use the right tool for the job. Measure what you're doing and don't believe the hype that CTE's, Temp Tables or Cursors are evil. Measure - because the truth depends on the scenario. May 19, 2018 at 13:34
  • 1
    @DaveHilditch that's a fair comment, but it's also fair comment to assert that in very, very many situations, cursors are not the right solution, so it's a workable generalization to have them as,well almost a last resort.
    – Mel Padden
    May 22, 2018 at 9:34
  • 2
    In my experience, a CURSOR is not bad in itself. CURSORS are commonly "wrongly" used by developers because in most programming languages, you have to think iteratively, as opposed to SQL where you mostly have to think in batches. I know this is a common mistake at my workplace where Devs just can't "see" a way out of a problem other than with a CURSOR, thus why a good DBA comes in handy to teach and correct them. @DaveHilditch is totally right: the right tool for the right job is all it takes.
    – Philippe
    Jul 31, 2018 at 17:54
17

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.

1
  • 6
    CTEs can be parameterized? How? Also, table variables are not always in-memory structures. See Martin's excellent answer to a related question.
    – Paul White
    Sep 30, 2016 at 6:14
14

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.

11

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.

0
8

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. ;)

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