Simple Talk is now part of the Redgate Community hub - find out why

SQL Server 2005 Common Table Expressions

Common Table Expressions (CTEs) are one of the most exciting features to be introduced with SQL Server 2005. Nigel Rivett explains what they are and how they can be used.

In my opinion, Common Table Expressions (CTEs) are one of the most exciting features to be introduced with SQL Server 2005. A CTE is a “temporary result set” that exists only within the scope of a single SQL statement. It allows access to functionality within that single SQL statement that was previously only available through use of functions, temp tables, cursors, and so on.

CTE basics

The concept behind a CTE is simplicity itself. Consider the following statement:

This defines a CTE called MyCTE. In brackets after the as keyword is the query that defines the CTE. The subsequent query references our CTE, in this case simply returning the string “hello”.

Like a derived table, a CTE lasts only for the duration of a query but, in contrast to a derived table, a CTE can be referenced multiple times in the same query. So, we now we have a way of calculating percentages and performing arithmetic using aggregates without repeating queries or using a temp table:

This returns (on my system):

Note that although this has only referenced sysobjects once in the CTE, the query plan will confirm that sysobjects is actually scanned 3 times – each aggregate in the result set causes an additional scan. As a result, it would still be more efficient to accumulate the values in a temporary table or table variable if you are accessing large tables.

CTE and recursion

More interesting, in my opinion, is the use of recursion with CTEs. The table defined in the CTE can be referenced in the CTE itself to give a recursive expression, using union all:

The query:

select x = convert(varchar(8000),’hello’)

is called the anchor member. This is executed in the first pass and will populate the CTE with the result, in this case hello. This initial CTE is repeatedly executed until the complete result set is returned. The next entry:

select x + ‘a’ from MyCTE where len(x) < 100

is a recursive member as it references the CTE, MyCTE. The recursive member is executed with the anchor member output to give helloa. The next pass takes helloa as input and returns helloaa, and so on so that we arrive at a CTE populated with rows as follows:

The recursion will terminate when the recursive member produces no rows – in this case recursion stops when the length of x equals 99 – or when the recursion limit is reached (more about that later). The CTE is then output by the following statement:

select x from MyCTE
order by

There are a few interesting issues associated with even this simple CTE usage. Note that the anchor member populates the CTE with a varchar(8000). Had the convert not been included then you would expect the datatype of x to be defined by that of the anchor member (varchar(5)) and to give an error when trying to insert the longer recursive entries. This does indeed happen – but you would expect varchar(1000) to be fine. Not so. This still produces the error that the datatypes don’t match. In fact, you should always cast the recursive member output to be the same as the anchor member:

But why, then, did the code work with varchar(8000)? This looks like a bug. As far as I can tell varchar(8000) and nvarchar(4000) seem to be the only values that work. Varchar(max) and varchar(7999) give an error as do all the other values that I have tried.

Multiple anchor members

Any entry that does not reference the CTE will be considered an anchor member so we can also include multiple anchor members using a union all:

This adds two rows from the anchor members and hence the recursive member acts on two rows for each pass to produce the output:

The first recursive pass produces the output “helloa” and “goodbyea”. The second produces “helloaa” and “goodbyeaa”. The third produces “helloaaa” and “goodbyeaaa”. For subsequent passes the string containing “goodbye” is too long for the check len(x)<10 but recursion continues as long as some output is produced – so we get more entries from hello than goodbye.

Multiple recursive members

We can also include multiple recursive members to produce extra rows at each pass:

This produces the result:

In order to understand this output, you need to remember that the input to each pass is the output from the previous pass. On the first recursive pass the input is the row from the anchor member. This produces two rows – one from each recursive member. On the next pass the input is the two rows output from the previous pass. Each recursive member outputs two rows producing four in total. In other words, the number of rows output doubles with each pass.

We can modify the output by limiting the rows on which the recursive members act. For example:


Another method is to flag the recursive members:

This gives the same result as above. It forces each recursive member to act only on the anchor member output and on any output that it itself has produced. This is at the expense of including the redundant data column “i” in the CTE but it does make the code a lot more readable. Using a similar technique we can output a value to indicate which pass produces each row:

This returns the following, r1 giving the pass number for the first recursive member and r2 for the second:

This is very useful for debugging and also for detecting how a CTE is processed and for controlling the number of passes for each recursive member. For instance:

Here I have terminated the first recursive member after two iterations and the second after five, giving:

Note that recursion continues until a pass produces no output – although the first recursive member terminates early recursion continues until both recursive members produce no output.

Recursion limit

In order to demonstrate the recursion limit, we start by producing a series of numbers in a CTE:

This happily produces the numbers 1 to 100 (a tally table). However, try increasing the recursion limit as follows:

You will receive the following error:

The statement terminated. The maximum recursion 100 has been exhausted before statement completion

It sounds like there is a limit of 100 for recursion but luckily this is just the default limit. For development this is very useful; to save time and space consider setting it lower for first attempts.

The maximum number of recursions can be set via the maxrecursion option, up to a maximum of 32767 (wouldn’t it be nice, though, if the error message suggested that the recursion limit should be increased?):

Uses for Common Table Expressions

Finally, let’s review some of the more interesting applications of CTEs.

Traversing a hierarchy

This is covered well in BOL and now gives SQL Server something to compete with the Oracle connect by operator.

Date Ranges

A common requirement is to aggregate entries per day (or month or year). This is easy using a group by, if there are entries for every day:


However, if there are some days with no transactions the result set, rather than report zero, does not give an entry for that day. To get round this we need to left join to a tally table which includes the days on which we wish to report. With a CTE this becomes simple. For example, for a year:

Note the “;” before the CTE definition – that’s just a syntax requirement if the CTE declaration is not the first statement in a batch.

Parsing CSV values

It is common to pass a CSV string in to a stored procedure as a means of passing in an array of values. Often this is turned into a table via a function. Using a CTE we can now contain this code within the stored procedure:

The output is as follows:

How does this work? The anchor member, select i=1, j=charindex(‘,’,@s+’,’), returns 1 and the location of the first comma. The recursive member gives the location of the first character after the comma and the location of the next comma (we append a comma to the string to get the last entry). The result set is then obtained by using these values in a substring.

In the previous example the CTE output was the start and end locations of each of the strings. We can instead produce the strings themselves; the CTE code becomes a little more complicated but the following query is simplified:

Beyond 32767

What happens if you want a list of numbers that extends beyond 32767? Although the recursion limit is 32767 it is possible to create extra entries. For example, the following CTE returns all the numbers from 0 to 64000. The result set produced is used to check the results. The maximum and count verify that all of the numbers are present, assuming that the result is a sequence:

To take this a step further we can accumulate values by manipulating the CTE:


In other words, this returns all the numbers from 0 to 1024031. How does this work? The derived table, n2, consists of all the numbers from 0 to 32 multiplied by 32001, i.e.


This is cross-joined with the result of the CTE, n, which consists of all the numbers from 0 to 32000, and the result is the sum of the values from n and n2. Hence we get:

from n 0- 32000 with 0 from n2 to give 0 – 32000
from n 0- 32000 with 32001 from n2 to give 32001 – 64001
from n 0- 32000 with 64002 from n2 to give 64002 – 96002
from n 0- 32000 with 96003 from n2 to give 64003 – 96003

to give the values 0 – 1,024,031. If more values are required then just increase the size of n2 by increasing the maximum value from 32.

Write SQL faster. As Nigel demonstrates, Common Table Expressions are one of the most powerful features to be introduced with SQL Server 2005. SQL Prompt, Red Gate’s code completion tool, offers full support for CTEs, including recursive CTEs and multiple CTEs within a single WITH statement, and will increase the speed and accuracy with which you create this and other SQL code.

How you log in to Simple Talk has changed

We now use Redgate ID (RGID). If you already have an RGID, we’ll try to match it to your account. If not, we’ll create one for you and connect it.

This won’t sign you up to anything or add you to any mailing lists. You can see our full privacy policy here.


Simple Talk now uses Redgate ID

If you already have a Redgate ID (RGID), sign in using your existing RGID credentials. If not, you can create one on the next screen.

This won’t sign you up to anything or add you to any mailing lists. You can see our full privacy policy here.