73

My question is similar to this MySQL question, but intended for SQL Server:

Is there a function or a query that will return a list of days between two dates? For example, lets say there is a function called ExplodeDates:

SELECT ExplodeDates('2010-01-01', '2010-01-13');

This would return a single column table with the values:

2010-01-01
2010-01-02
2010-01-03
2010-01-04
2010-01-05
2010-01-06
2010-01-07
2010-01-08
2010-01-09
2010-01-10
2010-01-11
2010-01-12
2010-01-13

I'm thinking that a calendar/numbers table might be able to help me here.


Update

I decided to have a look at the three code answers provided, and the results of the execution - as a % of the total batch - are:

Lower is better

I have accepted Rob Farley's answer, as it was the fastest, even though numbers table solutions (used by both KM and StingyJack in their answers) are something of a favourite of mine. Rob Farley's was two-thirds faster.

Update 2

Alivia's answer is much more succinct. I have changed the accepted answer.

Community
  • 1
  • 1
Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
  • Looping performance in SQL is going to S U C K. Please keep that in mind when trying out these answers. – StingyJack Sep 04 '09 at 12:31
  • 4
    What about exec time? % of total batch is used to identify a bottleneck, not throughput. Are you benchmarking the actual function call or everything else with it? Comparing the results of small and large batches? – StingyJack Sep 04 '09 at 15:49
  • 1
    using SET STATISTICS TIME ON all three functoions called with ('1/1/1998','12/31/2020') report back the same CPU time = 0 ms, elapsed time = 1 ms. When calling Rob's and mine with ('1/1/1900','1921-11-27'), StingyJacks can't do that date range I get Rob's as: CPU time = 93 ms, elapsed time = 93 ms. and I get mine: CPU time = 0 ms, elapsed time = 1 ms., mine looks way better. What testing method do you use @Dan Atkinson? if you included the one time Number table set-up, that is a VERY FLAWED way, as it does not reflect the actual in-use perfomrance. – KM. Sep 04 '09 at 17:05
  • @KM and @StingyJack. Thank you both for educating me on the correct way to benchmark. And KM, thank you for going to the trouble to point out the actual benchmark results. I will run some on my db and update the question accordingly. Thanks again! – Dan Atkinson Sep 04 '09 at 19:23
  • Why did you change the answer? Alivia's answer requires a hint to make sure it includes enough values, and it's not a function as was requested. – Rob Farley May 25 '17 at 07:00
  • @RobFarley I missed your comment but, in the intervening years since this question was asked, better answers appeared. If a better answer comes along, the new answer should be accepted. I asked about "a function **or a query**", and I did not mention anything about a hint not being desired or required. If you have a better or faster result to this, feel free to update your answer accordingly, or post a new one. – Dan Atkinson May 13 '19 at 13:49
  • The OP changed from the most correct answer to Alivia's because it was more "succinct". Really bad move because it's also much more CPU intensive, just as slow as a normal WHILE loop, slower than a transactional WHILE loop, and uses 8 times as much logical I/O than a normal WHILE loop. I STRONGLY recommend that this method (incrementing Recursive CTE or rCTE) be avoided. Rob Farely's answer is an iTVF (Inline Table Valued Function) making it even easier to use than the rCTE method. – Jeff Moden Dec 24 '21 at 13:55

21 Answers21

110

this few lines are the simple answer for this question in sql server.

WITH mycte AS
(
  SELECT CAST('2011-01-01' AS DATETIME) DateValue
  UNION ALL
  SELECT  DateValue + 1
  FROM    mycte   
  WHERE   DateValue + 1 < '2021-12-31'
)

SELECT  DateValue
FROM    mycte
OPTION (MAXRECURSION 0)
Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
Alivia
  • 1,312
  • 1
  • 10
  • 17
  • 1
    I wonder why '2011-01-01' is not working for me but '20110101' is. – greg121 Nov 30 '15 at 12:40
  • This is awesome! Readers: Set the where clause to include or exclude the final date per your requirements. up vote. – Yogi May 18 '18 at 22:19
  • 2
    The OP changed from the most correct answer to Alivia's because it was more "succinct". Really bad move because it's also much more CPU intensive, just as slow as a normal WHILE loop, slower than a transactional WHILE loop, and uses 8 times as much logical I/O than a normal WHILE loop. I STRONGLY recommend that this method (incrementing Recursive CTE or rCTE) be avoided. Rob Farely's answer is an iTVF (Inline Table Valued Function) making it even easier to use than the rCTE method. – Jeff Moden Dec 24 '21 at 13:52
  • You also cannot use this inside a view or a TVF as you are NOT able to use OPTION (MAXRECURSION 0) inside a view/function. I'm more curious how the other answers don't need the OPTION (MAXRECURSION 0) as that is the only solution for my use case. I absolutely am not able to use OPTION (MAXRECURSION 0). – Code Novice Jun 17 '22 at 21:05
72

Try something like this:

CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime)
returns table as
return (
with 
 N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,N5 as (SELECT 1 as n FROM N4 t1, N4 t2)
,N6 as (SELECT 1 as n FROM N5 t1, N5 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N6)
SELECT DATEADD(day,num-1,@startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1
);

You then use:

SELECT *
FROM dbo.ExplodeDates('20090401','20090531') as d;

Edited (after the acceptance):

Please note... if you already have a sufficiently large nums table then you should use:

CREATE FUNCTION dbo.ExplodeDates(@startdate datetime, @enddate datetime)
returns table as
return (
SELECT DATEADD(day,num-1,@startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,@startdate,@enddate) + 1
);

And you can create such a table using:

CREATE TABLE dbo.nums (num int PRIMARY KEY);
INSERT dbo.nums values (1);
GO
INSERT dbo.nums SELECT num + (SELECT COUNT(*) FROM nums) FROM nums
GO 20

These lines will create a table of numbers containing 1M rows... and far quicker than inserting them one by one.

You should NOT create your ExplodeDates function using a function that involves BEGIN and END, as the Query Optimizer becomes unable to simplify the query at all.

Rob Farley
  • 15,625
  • 5
  • 44
  • 58
  • Thank you for taking the time to improve your answer. I also wasn't aware that using BEGIN and END prevents Query Optimizer from doing its job. Thanks! – Dan Atkinson Sep 05 '09 at 10:57
  • 3
    If I could vote this up more than once I would - the performance of it is phenomenal. I tested it against a simple version where nums is a table of numbers, with a clustered index on the number. Where the date difference is 2 days, the CTE beats the clustered index by a factor of over 2 (28% vs 72%), but if the date difference is 37 years then the CTE version is 3% vs 97% for the table! I wish I knew why it was so quick... – Jonathan Sayce Sep 01 '11 at 12:56
  • It's because it's not having to do any I/O. – Rob Farley Sep 03 '11 at 12:29
  • 1
    THIS needs to be the accepted answer as it removes the restriction of having to use OPTION (MAXRECURSION 0). If you are needing to create a TVF or a View THIS is the only answer. – Code Novice Jun 17 '22 at 21:28
  • 1
    I have been convinced that this should indeed be the correct answer. – Dan Atkinson Aug 31 '22 at 16:37
18

This does exactly what you want, modified from Will's earlier post. No need for helper tables or loops.

WITH date_range (calc_date) AS (
    SELECT DATEADD(DAY, DATEDIFF(DAY, 0, '2010-01-13') - DATEDIFF(DAY, '2010-01-01', '2010-01-13'), 0)
        UNION ALL SELECT DATEADD(DAY, 1, calc_date)
            FROM date_range
            WHERE DATEADD(DAY, 1, calc_date) <= '2010-01-13')
SELECT calc_date
FROM date_range;
Jason
  • 197
  • 1
  • 2
  • 3
    I get the following error on more complex date sets: `The statement terminated. The maximum recursion 100 has been exhausted before statement completion.` So, I should point out for others who wish to use this answer on large ranges that you will need to add a maxrecursion value - `OPTION (MAXRECURSION 0)`. – Dan Atkinson Oct 23 '12 at 08:41
  • Same can't use this in some use cases. Unable to use this inside a view or TVF and probably some other scenarios where this just isn't possible. – Code Novice Jun 17 '22 at 21:07
6
DECLARE @MinDate DATETIME = '2012-09-23 00:02:00.000',
    @MaxDate DATETIME = '2012-09-25 00:00:00.000';

SELECT  TOP (DATEDIFF(DAY, @MinDate, @MaxDate) + 1) Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @MinDate)
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
Surinder Singh
  • 161
  • 2
  • 2
4

I'm an oracle guy, but I believe MS SQL Server has support for the connect by clause:

select  sysdate + level
from    dual
connect by level <= 10 ;

The output is:

SYSDATE+LEVEL
05-SEP-09
06-SEP-09
07-SEP-09
08-SEP-09
09-SEP-09
10-SEP-09
11-SEP-09
12-SEP-09
13-SEP-09
14-SEP-09

Dual is just a 'dummy' table that comes with oracle (it contains 1 row and the word 'dummy' as the value of the single column).

Brian
  • 13,412
  • 10
  • 56
  • 82
  • SQL Server doesn't have a built in table "dual", you need to create your own, like I do in my example code. I think "sysdate" is GETDATE() in SQL Server, and "connect by" is not valid syntax. – KM. Sep 04 '09 at 11:43
  • Thus you might also SELECT from nowhere in SQL Server. SELECT GETDATE() is a valid line of code in SQL Server, not in Oracle, even though you replace the GETDATE() function by its SYSDATE homologue. – Will Marcouiller Sep 04 '09 at 11:48
  • You're right Brian, in Oracle we would do it this way. There are much interesting features in Oracle and PL/SQL that are not contained in TSQL and SQL Server. this is Sybase's fault! ;-) SQL Server is primarly based on Sysbase TSQL language. – Will Marcouiller Sep 04 '09 at 11:49
  • _SELECT GETDATE()_ will not produce a set only a single row. using dual in Oracle, you get a set. – KM. Sep 04 '09 at 11:54
  • Little offtop: actually the column name is "DUMMY" and the value is "X". ;-) – WojtusJ Sep 18 '12 at 08:02
1

A few ideas:

If you need the list dates in order to loop through them, you could have a Start Date and Day Count parameters and do a while loop whilst creating the date and using it?

Use C# CLR Stored Procedures and write the code in C#

Do this outside the database in code

Mark Redman
  • 24,079
  • 20
  • 92
  • 147
  • CLR Stored proc is def the way to go if performance is critical. – StingyJack Sep 04 '09 at 11:59
  • @StingyJack, no way. a Numbers table would be much more efficient, see my answer for an example of how. – KM. Sep 04 '09 at 12:34
  • Performance is not critical, as this would only be called once an hour at worst, and on average, once a day, and then it's cached. I don't want to use a CLR to do this though. – Dan Atkinson Sep 04 '09 at 13:30
  • @KM - at risk of starting a flame war here, you should know that SQL is not designed to handle procedural ops, and performs poorly with them. If you need to do something like that, its best handled by application code. – StingyJack Sep 04 '09 at 13:39
  • @StingyJack, how is my function procedural? other than the validation check, it is a simple query that adding an offset to a fixed date for a viariable number of rows. Its not much different that caluclations done on in item details, like calculating total price based on qty and unit price with or without a currency. – KM. Sep 04 '09 at 14:35
1

Would all these dates be in the database already or do you just want to know the days between the two dates? If it's the first you could use the BETWEEN or <= >= to find the dates between

EXAMPLE:

SELECT column_name(s)
FROM table_name
WHERE column_name
BETWEEN value1 AND value2

OR

SELECT column_name(s)
FROM table_name
WHERE column_name
value1 >= column_name
AND column_name =< value2
Phill Pafford
  • 83,471
  • 91
  • 263
  • 383
1

All you have to do is just change the hard coded value in the code provided below

DECLARE @firstDate datetime
    DECLARE @secondDate datetime
    DECLARE @totalDays  INT
    SELECT @firstDate = getDate() - 30
    SELECT @secondDate = getDate()

    DECLARE @index INT
    SELECT @index = 0
    SELECT @totalDays = datediff(day, @firstDate, @secondDate)

    CREATE TABLE #temp
    (
         ID INT NOT NULL IDENTITY(1,1)
        ,CommonDate DATETIME NULL
    )

    WHILE @index < @totalDays
        BEGIN

            INSERT INTO #temp (CommonDate) VALUES  (DATEADD(Day, @index, @firstDate))   
            SELECT @index = @index + 1
        END

    SELECT CONVERT(VARCHAR(10), CommonDate, 102) as [Date Between] FROM #temp

    DROP TABLE #temp
Taryn
  • 242,637
  • 56
  • 362
  • 405
Shiva
  • 1,379
  • 1
  • 15
  • 32
1

A Bit late to the party, but I like this solution quite a bit.

CREATE FUNCTION ExplodeDates(@startDate DateTime, @endDate DateTime)
RETURNS table as
return (
    SELECT  TOP (DATEDIFF(DAY, @startDate, @endDate) + 1)
            DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @startDate) AS DATE
    FROM    sys.all_objects a
            CROSS JOIN sys.all_objects b
            )
Schuits
  • 41
  • 6
1

This query works on Microsoft SQL Server.

select distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate
       from (
             SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
             FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
                    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
                    (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
                  (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
       ) a
       where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)
       order by aDate asc;

Now let's look at how it works.

The inner query merely returns a list of integers from 0 to 9999. It will give us a range of 10,000 values for calculating dates. You can get more dates by adding rows for ten_thousands and hundred_thousands and so forth.

SELECT ones.n + 10 * tens.n + 100 * hundreds.n + 1000 * thousands.n as v
         FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
                (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
                (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
              (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n)
   ) a;

This part converts the string to a date and adds a number to it from the inner query.

cast('2010-01-01' as datetime) + ( a.v / 10 )

Then we convert the result into the format you want. This is also the column name!

format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' )

Next we extract only the distinct values and give the column name an alias of aDate.

distinct format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) as aDate

We use the where clause to filter in only dates within the range you want. Notice that we use the column name here since SQL Server does not accept the column alias, aDate, within the where clause.

where format( cast('2010-01-01' as datetime) + ( a.v / 10 ), 'yyyy-MM-dd' ) < cast('2010-01-13' as datetime)

Lastly, we sort the results.

   order by aDate asc;
RichS
  • 540
  • 1
  • 6
  • 12
0

Before you use my function, you need to set up a "helper" table, you only need to do this one time per database:

CREATE TABLE Numbers
(Number int  NOT NULL,
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
    SET @x=@x+1
    INSERT INTO Numbers VALUES (@x)
END

here is the function:

CREATE FUNCTION dbo.ListDates
(
     @StartDate    char(10)  
    ,@EndDate      char(10)
)
RETURNS
@DateList table
(
    Date datetime
)
AS
BEGIN


IF ISDATE(@StartDate)!=1 OR ISDATE(@EndDate)!=1
BEGIN
    RETURN
END

INSERT INTO @DateList
        (Date)
    SELECT
        CONVERT(datetime,@StartDate)+n.Number-1
        FROM Numbers  n
        WHERE Number<=DATEDIFF(day,@StartDate,CONVERT(datetime,@EndDate)+1)


RETURN

END --Function

use this:

select * from dbo.ListDates('2010-01-01', '2010-01-13')

output:

Date
-----------------------
2010-01-01 00:00:00.000
2010-01-02 00:00:00.000
2010-01-03 00:00:00.000
2010-01-04 00:00:00.000
2010-01-05 00:00:00.000
2010-01-06 00:00:00.000
2010-01-07 00:00:00.000
2010-01-08 00:00:00.000
2010-01-09 00:00:00.000
2010-01-10 00:00:00.000
2010-01-11 00:00:00.000
2010-01-12 00:00:00.000
2010-01-13 00:00:00.000

(13 row(s) affected)
KM.
  • 101,727
  • 34
  • 178
  • 212
  • Where there's a "WHILE" there's a loop! – StingyJack Sep 04 '09 at 12:28
  • 5
    @StingyJack, are you nuts, there is no loop in my function. I use a loop to set up the Numbers table so people can easily see what it does. I could easily use a CTE there (like from here: http://www.sommarskog.se/arrays-in-sql-2005.html#tblnum), but it confuses some people. For a one time set-up of a table it is not an issue. – KM. Sep 04 '09 at 12:30
  • 1
    He's refering to where you input values into your Numbers table. – Mr. Smith Sep 04 '09 at 12:31
  • @KM.. there is certainly a loop in your code. Do me a favor before you bash me... test how long it takes to create the table of numbers using yours and my method for 10000 numbers. You will see the one I borrowed from Moden will beat the looped op by several orders of magnitude. Now after that, test how long each function takes. I am willing to bet that the solution I provided will perform better than the one you got. Next time you want to DV someone, you better make sure that your reasons are well founded. – StingyJack Sep 04 '09 at 13:44
  • 1
    I love the idea of numbers tables! They're ridiculously versatile and can be used for other things as well. – Dan Atkinson Sep 04 '09 at 14:20
  • @StingyJack, I did try your function, but it didn't work! you have an error in your code, once I fixed that if you run _SELECT * FROM dbo.ListDates('1/1/1997','12/31/1997') you get no dates back, but on mine you do. For my function you get dates no mater what starting date you use (1/1/1492, or 12/31/2500), for up to 8000 dates. You load your number table fast, but have to make it a throw aray #temp table to use IDENTITY(1,1) trick. The way I see it, set up a good number table one time and use it for many things. my numbers table took 2 seconds to set up, but I have to use many other times. – KM. Sep 04 '09 at 14:23
  • @StingyJack, FYI - when I run both your function and my function for ('1/1/1998','12/31/2020') in SSMS, they both return 8401 rows in 0 seconds – KM. Sep 04 '09 at 14:26
  • I ran a comparison on the three answers I felt were good, and yes, the execution times for your answer and StingyJack's was identical. Rob Farley's answer, however was about two-thirds (69%) as fast. Whilst performance is certainly not a critical issue in my case (this query isn't going to be run very often, and will have a very small date range to return), it is definitely a sway. I do agree with KM's argument that creating a temp number table (something that is enormously helpful) is pointless, and should be kept around for other uses. – Dan Atkinson Sep 04 '09 at 14:53
  • using SET STATISTICS TIME ON all three functoions called with ('1/1/1998','12/31/2020') report back the same CPU time = 0 ms, elapsed time = 1 ms. When calling Rob's and mine with ('1/1/1900','1921-11-27'), StingyJacks can't do that date range I get Rob's as: CPU time = 93 ms, elapsed time = 93 ms. and I get mine: CPU time = 0 ms, elapsed time = 1 ms., mine looks way better. What testing method do you use @Dan Atkinson? if you included the one time Number table set-up, that is a VERY FLAWED way, as it does not reflect the actual in-use perfomrance – KM. Sep 04 '09 at 17:08
  • My code doesn't have a pre-existing numbers table. If you already have the numbers table, then you should use SELECT DATEADD(day,num-1,@startdate) as thedateFROM numsWHERE num <= DATEDIFF(day,@startdate,@enddate) + 1... But do it in one statement, using "returns table as return (...)", so that the Query Optimizer cacn simplify it out. – Rob Farley Sep 05 '09 at 01:19
  • Also - why on earth would you make a function to handle dates that uses char(10) parameters? Why not make it date/datetime? – Rob Farley Sep 05 '09 at 01:26
  • @Rob Farley, you don't need to use DATEADD(day,...) you can just "+" and "-" to a datetime to work on day increments. Actually your number table query has the same execution plan as mine. Also I use the char(10) to remove time (which your function does not do, was it necessary? OP never says, I did it anyway) and allow the function to validate the date without failing when called. OP didn't say if validation was necessary, so I threw it in just in case, you can easily remove it if needed and then return the query as you suggest. – KM. Sep 09 '09 at 12:04
0

Definately a numbers table, though tyou may want to use Mark Redman's idea of a CLR proc/assembly if you really need the performance.

How to create the table of dates (and a super fast way to create a numbers table)

/*Gets a list of integers into a temp table (Jeff Moden's idea from SqlServerCentral.com)*/
 SELECT TOP 10950 /*30 years of days*/
        IDENTITY(INT,1,1) as N
   INTO #Numbers
   FROM Master.dbo.SysColumns sc1,
        Master.dbo.SysColumns sc2


/*Create the dates table*/
CREATE TABLE [TableOfDates](
    [fld_date] [datetime] NOT NULL,
 CONSTRAINT [PK_TableOfDates] PRIMARY KEY CLUSTERED 
(
    [fld_date] ASC
)WITH FILLFACTOR = 99 ON [PRIMARY]
) ON [PRIMARY]

/*fill the table with dates*/
DECLARE @daysFromFirstDateInTheTable int
DECLARE @firstDateInTheTable DATETIME

SET @firstDateInTheTable = '01/01/1998'
SET @daysFromFirstDateInTheTable = (SELECT (DATEDIFF(dd, @firstDateInTheTable ,GETDATE()) + 1))

INSERT INTO
      TableOfDates
SELECT 
      DATEADD(dd,nums.n - @daysFromFirstDateInTheTable, CAST(FLOOR(CAST(GETDATE() as FLOAT)) as DateTime)) as FLD_Date
FROM #Numbers nums

Now that you have a table of dates, you can use a function (NOT A PROC) like KM's to get the table of them.

CREATE FUNCTION dbo.ListDates
(
     @StartDate    DATETIME  
    ,@EndDate      DATETIME
)
RETURNS
@DateList table
(
    Date datetime
)
AS
BEGIN

/*add some validation logic of your own to make sure that the inputs are sound.Adjust the rest as needed*/

  INSERT INTO
    @DateList
  SELECT FLD_Date FROM TableOfDates (NOLOCK) WHERE FLD_Date >= @StartDate AND FLD_Date <= @EndDate
  RETURN
END
StingyJack
  • 19,041
  • 10
  • 63
  • 122
  • why do you need a table of dates, you just calculate them using your Numbers table?? – KM. Sep 04 '09 at 12:36
  • because calculating them on the fly can cause poor performance, especially if they are used inline and evaluated for each row accessed by the statement. – StingyJack Sep 04 '09 at 13:37
  • **Msg 137, Level 15, State 2, Line 23 Must declare the scalar variable "@".** this _(SELECT (DATEDIFF(dd, @ firstDateInTheTable ,GETDATE()) + 1))_ should be (SELECT (DATEDIFF(dd, @firstDateInTheTable ,GETDATE()) + 1))_ – KM. Sep 04 '09 at 14:30
0

Perhaps if you wish to go an easier way, this should do it.

WITH date_range (calc_date) AS (
    SELECT DATEADD(DAY, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP) - 6, 0)
        UNION ALL SELECT DATEADD(DAY, 1, calc_date)
            FROM date_range
            WHERE DATEADD(DAY, 1, calc_date) < CURRENT_TIMESTAMP)
SELECT calc_date
FROM date_range;

But the temporary table is a very good approach also. Perhaps shall you also consider a populated calendar table.

Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
  • You only have to create a stored procedure with this code, and perhaps replace the CURRENT_TIMESTAMP values with yours or something like this. – Will Marcouiller Sep 04 '09 at 12:24
0
Declare @date1 date = '2016-01-01'
              ,@date2 date = '2016-03-31'
              ,@date_index date
Declare @calender table (D date)
SET @date_index = @date1
WHILE @date_index<=@date2
BEGIN
INSERT INTO @calender
SELECT @date_index

SET @date_index = dateadd(day,1,@date_index)

IF @date_index>@date2
Break
ELSE
Continue
END
Krish
  • 9
  • 6
0

-- ### Six of one half dozen of another. Another method assuming MsSql

Declare @MonthStart    datetime   = convert(DateTime,'07/01/2016')
Declare @MonthEnd      datetime   = convert(DateTime,'07/31/2016')
Declare @DayCount_int       Int   = 0 
Declare @WhileCount_int     Int   = 0

set @DayCount_int = DATEDIFF(DAY, @MonthStart, @MonthEnd)
select @WhileCount_int
WHILE @WhileCount_int < @DayCount_int + 1
BEGIN
   print convert(Varchar(24),DateAdd(day,@WhileCount_int,@MonthStart),101)
   SET @WhileCount_int = @WhileCount_int + 1;
END;
M T Head
  • 1,085
  • 9
  • 13
0

In case you want to print years starting from a particular year till current date. Just altered the accepted answer.

WITH mycte AS
    (
      SELECT YEAR(CONVERT(DATE, '2006-01-01',102)) DateValue
      UNION ALL
      SELECT  DateValue + 1
      FROM    mycte   
      WHERE   DateValue + 1 < = YEAR(GETDATE())
    )
    SELECT  DateValue
    FROM    mycte

OPTION (MAXRECURSION 0)
MNH
  • 13
  • 5
0

if you're in a situation like me where procedures and functions are prohibited, and your sql user does not have permissions for insert, therefore insert not allowed, also "set/declare temporary variables like @c is not allowed", but you want to generate a list of dates in a specific period, say current year to do some aggregation, use this

select * from 
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
 (select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
 (select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
 (select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
 (select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
 (select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31'
computingfreak
  • 4,939
  • 1
  • 34
  • 51
-1
WITH TEMP (DIA, SIGUIENTE_DIA ) AS
           (SELECT 
               1, 
               CAST(@FECHAINI AS DATE)
            FROM 
               DUAL
           UNION ALL
            SELECT 
               DIA, 
               DATEADD(DAY, DIA, SIGUIENTE_DIA)
            FROM 
               TEMP
            WHERE
               DIA < DATEDIFF(DAY,  @FECHAINI, @FECHAFIN)   
               AND DATEADD(DAY, 1, SIGUIENTE_DIA) <=  CAST(@FECHAFIN AS DATE)
           )
           SELECT 
              SIGUIENTE_DIA AS CALENDARIO 
           FROM
              TEMP
           ORDER BY   
              SIGUIENTE_DIA

The detail is on the table DUAL but if your exchange this table for a dummy table this works.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
-1
SELECT  dateadd(dd,DAYS,'2013-09-07 00:00:00') DATES
INTO        #TEMP1
FROM
(SELECT TOP 365 colorder - 1 AS DAYS from master..syscolumns 
    WHERE id = -519536829 order by colorder) a

WHERE datediff(dd,dateadd(dd,DAYS,'2013-09-07 00:00:00'),'2013-09-13 00:00:00' ) >= 0 
    AND  dateadd(dd,DAYS,'2013-09-07 00:00:00') <=  '2013-09-13 00:00:00'  
    SELECT * FROM #TEMP1
Joel
  • 7,401
  • 4
  • 52
  • 58
ASEEM
  • 1
-1

Answer is avialbe here How to list all dates between two dates

Create Procedure SelectDates(@fromDate Date, @toDate Date)
AS
BEGIN
    SELECT DATEADD(DAY,number,@fromDate) [Date]
    FROM master..spt_values
    WHERE type = 'P'
    AND DATEADD(DAY,number,@fromDate) < @toDate

END
Community
  • 1
  • 1
Chitta
  • 206
  • 2
  • 6
  • This is not a good answer for several reasons. 1: Master table isn't always available. 2: The table is only as long as the number of items in your database. If this is less than the actual answer, then the returned list of this proc will be incorrect. 3: The answer is, more or less, a numbers table which uses a system table. – Dan Atkinson Jun 23 '14 at 10:07
-1
DECLARE @StartDate DATE = '2017-09-13',         @EndDate DATE = '2017-09-16'

SELECT date  FROM (   SELECT DATE = DATEADD(DAY, rn - 1, @StartDate)   FROM    (
    SELECT TOP (DATEDIFF(DAY, @StartDate, DATEADD(DAY,1,@EndDate)))
      rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    ORDER BY s1.[object_id]   ) AS x ) AS y

Result:

2017-09-13

2017-09-14

2017-09-15

2017-09-16
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Mr.D
  • 1