Common Table Expressions (CTEs) were 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:

with MyCTE(x)

as

(select x='hello')

select x from MyCTE

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:

with MyCTE(x)

as

(

select top 10 x = id from sysobjects

)

select x, maxx = (select max(x) from MyCTE), pct =

100.0 * x / (select sum(x) from MyCTE)

from MyCTE

This returns (on my system):

**x maxx pct **4 2137058649 2.515723270440

5 2137058649 3.144654088050

7 2137058649 4.402515723270

8 2137058649 5.031446540880

13 2137058649 8.176100628930

15 2137058649 9.433962264150

25 2137058649 15.723270440251

26 2137058649 16.352201257861

27 2137058649 16.981132075471

29 2137058649 18.238993710691

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:

with MyCTE(x)

as

(

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

union all

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

)

select x from MyCTE

order by x

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:

hello

helloa

helloaa

helloaaa

helloaaaa

…

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 x

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:

with MyCTE(x)

as

(

select x = convert(varchar(1000),'hello')

union all

select convert(varchar(1000),x + 'a') from MyCTE

where len(x) < 100

)

select x from MyCTE

order by x

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:

with MyCTE(x)

as

(

select x = convert(varchar(1000),'hello')

union all

select x = convert(varchar(1000),'goodbye')

union all

select convert(varchar(1000),x + 'a') from MyCTE

where len(x) < 10

)

select x from MyCTE

order by len(x), x

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

hello

helloa

goodbye

helloaa

goodbyea

helloaaa

goodbyeaa

helloaaaa

goodbyeaaa

helloaaaaa

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:

with MyCTE(x)

as

(

select x = convert(varchar(1000),'hello')

union all

select convert(varchar(1000),x + 'a') from MyCTE where len(x) < 10

union all

select convert(varchar(1000),x + 'b') from MyCTE where len(x) < 10

)

select x from MyCTE

order by len(x), x

This produces the result:

hello

helloa

hellob

helloaa

helloab

helloba

hellobb

helloaaa

helloaab

helloaba

helloabb

hellobaa

hellobab

hellobba

hellobbb

helloaaaa

….

63 rows of output.

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:

with MyCTE(x)

as

(

select x = convert(varchar(1000),'hello')

union all

select convert(varchar(1000),x + 'a') from MyCTE

where len(x) < 10 **and (len(x) = 5 or x like '%a')**union all

select convert(varchar(1000),x + 'b') from MyCTE

where len(x) < 10

**and (len(x) = 5 or x like '%b')**

)

select x from MyCTE

order by len(x), x

giving:

hello

helloa

hellob

helloaa

hellobb

helloaaa

hellobbb

helloaaaa

hellobbbb

helloaaaaa

hellobbbbb

Another method is to flag the recursive members:

with MyCTE(i, x)

as

(

select i = 0, x = convert(varchar(1000),'hello')

union all

select i = 1, convert(varchar(1000),x + 'a') from MyCTE

where len(x) < 10 and i in (0,1)

union all

select i = 2, convert(varchar(1000),x + 'b') from MyCTE

where len(x) < 10 and i in (0,2)

)

select x from MyCTE

order by len(x), x

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:

with MyCTE(r1, r2, i, x)

as

(

select r1 = 1, r2 = 1, i = 0, x = convert(varchar(1000),'hello')

union all

select r1 = r1 + 1, r2 = r2, i = 1, convert(varchar(1000),x + 'a') from MyCTE

where len(x) < 10 and i in (0,1)

union all

select r1 = r1, r2 = r2 + 1, i = 2, convert(varchar(1000),x + 'b') from MyCTE

where len(x) < 10 and i in (0,2)

)

select r1, r2, x from MyCTE

order by len(x), x

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

**r1 r2 x**1 1 hello

2 1 helloa

1 2 hellob

3 1 helloaa

1 3 hellobb

4 1 helloaaa

1 4 hellobbb

5 1 helloaaaa

1 5 hellobbbb

6 1 helloaaaaa

1 6 hellobbbbb

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:

with MyCTE(r1, r2, i, x)

as

(

select r1 = 1, r2 = 1, i = 0, x = convert(varchar(1000),'hello')

union all

select r1 = r1 + 1, r2 = r2, i = 1, convert(varchar(1000),x + 'a') from MyCTE

where i in (0,1) and R1 < 3

union all

select r1 = r1, r2 = r2 + 1, i = 2, convert(varchar(1000),x + 'b') from MyCTE

where i in (0,2) and R2 < 6

)

select r1, r2, x from MyCTE

order by len(x), x

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

1 1 hello

2 1 helloa

1 2 hellob

3 1 helloaa

1 3 hellobb

1 4 hellobbb

1 5 hellobbbb

1 6 hellobbbbb

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:

with MyCTE(i)

as

(

select i = 1

union all

select i = i + 1 from MyCTE where i < 100

)

select i

from MyCTE

order by i

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

with MyCTE(i)

as

(

select i = 1

union all

select i = i + 1 from MyCTE where i < 1000

)

select i

from MyCTE

order by i

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?):

with MyCTE(i)

as

(

select i = 1

union all

select i = i + 1 from MyCTE where i < 1000

)

select i

from MyCTE

order by i

option (maxrecursion 1000)

### 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:

declare @Sales table (TrDate datetime, Amount money)

insert @Sales select '20060501', 200

insert @Sales select '20060501', 400

insert @Sales select '20060502', 1200

select [day] = TrDate, sum(amount) total_sales

from @sales

group by TrDate

order by TrDate

giving:

**day total_sales**2006-05-01 00:00:00.000 600.00

2006-05-02 00:00:00.000 1200.00

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:

declare @Sales table (TrDate datetime, Amount money)

insert @Sales select '20060501', 200

insert @Sales select '20060501', 400

insert @Sales select '20060502', 1200

;with MyCTE(d)

as

(

select d = convert(datetime,'20060101')

union all

select d = d + 1 from MyCTE where d < '20061231'

)

select [day] = d.d, sum(coalesce(s.amount,0))

from MyCTE d

left join @sales s

on s.TrDate = d.d

group by d.d

order by d.d

option (maxrecursion 1000)

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:

declare @s varchar(1000)

select @s = 'a,b,cd,ef,zzz,hello'

;with csvtbl(i,j)

as

(

select i=1, j=charindex(',',@s+',')

union all

select i=j+1, j=charindex(',',@s+',',j+1) from csvtbl

where charindex(',',@s+',',j+1) <> 0

)

select substring(@s,i,j-i)

from csvtbl

The output is as follows:

a

b

cd

ef

zzz

hello

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:

declare @s varchar(1000)

select @s = 'a,b,cd,ef,zzz,hello'

;with csvtbl(i,j, s)

as

(

select i=1, s=charindex(',',@s+','),

substring(@s, 1, charindex(',',@s+',')-1)

union all

select i=j+1, j=charindex(',',@s+',',j+1),

substring(@s, j+1, charindex(',',@s+',',j+1)-(j+1))

from csvtbl where charindex(',',@s+',',j+1) <> 0

)

select s from csvtbl

### 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:

with n(rc,i)

as

(

select rc = 1, i = 0

union all

select rc = 1, i = i + 1 from n where rc = 1 and i < 32000

union all

select rc = 2, i = i + 32001 from n where rc = 1 and i < 32000

)

select count_i = count(*), max_i = max(i)

from n

option (maxrecursion 32000)

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

`with `

`n `

`(`

`j`

`)`

`as `

`( `

`select `

`j `

`= `

`0`

`union `

`all`

`select `

`j `

`= `

`j `

`+ `

`1 `

`from `

`n `

`where `

`j `

`< `

`32000`

`)`

`select `

`max_i `

`= `

`max`

` (`

`na.i`

`), `

`count_i `

`= `

`count`

`(*)`

`from `

`( `

`select `

`i `

`= `

`j `

`+ `

`k`

` `

`from `

`n`

` `

`cross `

`join`

` `

`( `

`select `

`k `

`= `

`j `

`* `

`32001`

` `

`from `

`n`

` `

`where `

`j `

`< `

`32`

` `

`) `

`n2`

`) `

`na`

`option `

`(`

maxrecursion` `

`32000`

`)`

giving:

**max_i** **count_i**

----------- -------

1024031 1024032

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.

0

32001

64002

96003

….

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.