One of the best enhancements in T-SQL with SQL Server 2005 was Common Table Expressions(CTEs). CTEs are very helpful in writing more readable and manageable queries. The good things don’t end here; self-referencing CTEs are a very powerful method of implementing recursion in SQL queries. In this post, I will present a few examples of generating sequences using CTEs.
The following statements create a number sequence from 1 to 1000.
--define start and end limits
Declare @start int, @end int
Select @start=1, @end=1000
;With NumberSequence( Number ) as
(
Select @start as Number
union all
Select Number + 1
from NumberSequence
where Number < @end
)
--select result
Select * From NumberSequence Option (MaxRecursion 1000)
Notice that when selecting records from the CTE, we are setting a value for MaxRecursion Option. This one is important since the maximum recursion level for a CTE is set to 100 by default, and so any sequence with more than 100 rows will generate an error. We can turn the recursion limit off by using MaxRecursion 0, but this is not a good idea since it opens the possibility of infinite recursion in case of a small error in CTE.
Here’s another example that creates date sequence from 2009-03-01 to 2009-04-10.
--define start and end limits
Declare @todate datetime, @fromdate datetime
Select @fromdate='2009-03-01', @todate='2009-04-10'
;With DateSequence( Date ) as
(
Select @fromdate as Date
union all
Select dateadd(day, 1, Date)
from DateSequence
where Date < @todate
)
--select result
Select * from DateSequence option (MaxRecursion 1000)
This one generates an output similar to:

Notice, that this example, that generates date sequences can also be used to create Time Dimension for a Data Warehouse. If the granularity of time dimension is not too fine, CTEs give a nice alternative to loops. Here’s an example:
--define limits Declare @todate datetime, @fromdate datetime set @fromdate = '2009-01-01' set @todate = '2009-12-31' ;With DateSequence( [Date] ) as ( Select @fromdate as [Date] union all Select dateadd(day, 1, [Date]) from DateSequence where Date < @todate ) --select result Select CONVERT(VARCHAR,[Date],112) as ID, [Date] as [Date], DATEPART(DAY,[Date]) as [Day], CASE WHEN DATEPART(DAY,[Date]) = 1 THEN CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'st' WHEN DATEPART(DAY,[Date]) = 2 THEN CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'nd' WHEN DATEPART(DAY,[Date]) = 3 THEN CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'rd' ELSE CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'th' END as [DaySuffix], DATENAME(dw, [Date]) as [DayOfWeek], DATEPART(DAYOFYEAR,[Date]) as [DayOfYear], DATEPART(WEEK,[Date]) as [WeekOfYear], DATEPART(WEEK,[Date]) + 1 - DATEPART(WEEK,CAST(DATEPART(MONTH,[Date]) AS VARCHAR) + '/1/' + CAST(DATEPART(YEAR,[Date]) AS VARCHAR)) as [WeekOfMonth], DATEPART(MONTH,[Date]) as [Month], DATENAME(MONTH,[Date]) as [MonthName], DATEPART(QUARTER,[Date]) as [Quarter], CASE DATEPART(QUARTER,[Date]) WHEN 1 THEN 'First' WHEN 2 THEN 'Second' WHEN 3 THEN 'Third' WHEN 4 THEN 'Fourth' END as [QuarterName], DATEPART(YEAR,[Date]) as [Year] from DateSequence option (MaxRecursion 10000)
And here’s the beautiful output of the above query to be used as a time dimension (click to enlarge the image):


July 1, 2009 at 3:57 PM
Thank you for sharing , Application of CTE is easily understandable with your example.
September 3, 2009 at 11:25 PM
[...] once blogged about creating a sequence of numbers/dates using a recursive CTE in this post. The same technique can be used here. However, since the YearMonth column in the sample data [...]
September 11, 2009 at 12:25 PM
Great info! I thank your for sharing with us.
September 20, 2009 at 6:36 AM
That what i looking for, Thank you !!!
September 30, 2009 at 5:52 AM
Thank you for posting this – I needed a quick Time dimension, but do not have access to create it on the server as it is a client project. This method worked great for the report.
January 11, 2010 at 3:50 PM
[...] This is something I have used many times, and always had to look up on internet. I’m designing a new datawarehouse and have to populate my date dimension. In SQL server 2008 we have common table expressions (CTE), We can use it to run a sequence from to a date. This blog post will explain how you can generate dates: http://smehrozalam.wordpress.com/2009/06/09/t-sql-using-common-table-expressions-cte-to-generate-seq… [...]
January 25, 2010 at 10:19 AM
Thanks for this posting! I enjoyed reading it, and applying it.
I have, however, made 2 improvements for performance gain.
1st:
The case from line 20-26 has been reduced with the first case and all the RIGHT STATEMENTS. The RIGHT function is, off course, very expense.
CASE WHEN DATEPART(DAY,[Date]) = 1 THEN CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'st' WHEN DATEPART(DAY,[Date]) = 2 THEN CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'nd' WHEN DATEPART(DAY,[Date]) = 3 THEN CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'rd' ELSE CAST(DATEPART(DAY,[Date]) AS VARCHAR) + 'th' END as [DaySuffix]2nd:
I’ve removed the complete case from line 27-35 and replaced it with:
January 25, 2010 at 2:36 PM
Thanks very much for sharing this, Paul.
March 18, 2010 at 2:42 PM
I also found the same changes as Paul and when i wanted to post them i just saw his post.
But due to his to issues i found a third one. The WeekOfYear depending of where you live (and if you have SQL Server 2008) should be
DATEPART(ISO_WEEK,[Date]) as [WeekOfYear]
July 15, 2010 at 8:04 PM
This is great.
February 2, 2012 at 12:13 PM
Thanks for the Detailed Example