38

Is it possible to get month names between two dates in SQl

ie, 2011-05-01 And 2011-08-01 are the inputs I just want the output as

------------
Month
------------
May
June
July
August

If any body knows the query please share.

Nithesh Narayanan
  • 11,481
  • 34
  • 98
  • 138

8 Answers8

54
DECLARE @StartDate  DATETIME,
        @EndDate    DATETIME;

SELECT   @StartDate = '20110501'        
        ,@EndDate   = '20110801';


SELECT  DATENAME(MONTH, DATEADD(MONTH, x.number, @StartDate)) AS MonthName
FROM    master.dbo.spt_values x
WHERE   x.type = 'P'        
AND     x.number <= DATEDIFF(MONTH, @StartDate, @EndDate);

Results:

MonthName
------------------------------
May
June
July
August

(4 row(s) affected)
Bogdan Sahlean
  • 19,233
  • 3
  • 42
  • 57
  • Hi, I know that this is old answer, but can u explain how it works? I mean, I made select from master.dbo.spt_values, with type 'p', but it looks like just set of numbers. It is working for me, but I want to understand, how it behaves. – Valentyn Vynogradskiy Jul 23 '14 at 07:41
  • 1
    If you execute `SELECT x.number FROM master.dbo.spt_values x WHERE x.type = 'P' ` then you will get all values between 0 and 2047 (2048 values). – Bogdan Sahlean Jul 23 '14 at 12:30
  • So it is just numbers, it doesn't have any relation to dates. – Valentyn Vynogradskiy Jul 24 '14 at 10:59
  • 1
    @ValentynVynogradskiy: True but `DATEADD(MONTH, x.number, @StartDate)` will generate all days (date/time values) between `@StartDate` and `@EndDate` where x.number can take values from `0` to (maximum) `2048`. – Bogdan Sahlean Jul 25 '14 at 06:30
36

You can do this with a recursive CTE, by building up a table of dates, and getting the month name from each:

declare @start DATE = '2011-05-01'
declare @end DATE = '2011-08-01'

;with months (date)
AS
(
    SELECT @start
    UNION ALL
    SELECT DATEADD(month,1,date)
    from months
    where DATEADD(month,1,date)<=@end
)
select Datename(month,date) from months
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • 1
    One of the edge case of this is if you change the date to @start='2011-05-19' and @end='2011-08-15' it will only throw 3 months ie wrong it should display 4 months – Biju jose Sep 29 '16 at 09:03
  • This may help others: select Datename(month,date) from months option (maxrecursion 0) – StinkySocks Apr 05 '18 at 14:54
14

I've modified Jamiec's answer to output the Last day of the month as well.

declare @start DATE = '2014-05-01'
declare @end DATE = getdate()

;with months (date)
AS
(
    SELECT @start
    UNION ALL
    SELECT DATEADD(month, 1, date)
    from months
    where DATEADD(month, 1, date) < @end
)
select     [MonthName]    = DATENAME(mm, date),
           [MonthNumber]  = DATEPART(mm, date),  
           [LastDayOfMonth]  = DATEPART(dd, EOMONTH(date)),
           [MonthYear]    = DATEPART(yy, date)
from months

Which gives output:

MonthName   MonthNumber LastDayOfMonth  MonthYear
May         5           31              2014
June        6           30              2014
July        7           31              2014
August      8           31              2014
September   9           30              2014
DonSleza4e
  • 562
  • 6
  • 11
Elan Hasson
  • 1,214
  • 1
  • 19
  • 29
  • care ful with this code if `@start>@end` then it will not show the `@end` month. – Biju jose Sep 29 '16 at 10:23
  • 1
    if '@startdate='2014-05-19'` and `enddate='2014-08-15' then your output will not show the month of 'August'. I hope that should be also shown in the result – Biju jose Sep 29 '16 at 15:09
2

Inspired by Jamiec's answer, but fixing issue with with from day bigger then to day:

declare @start DATE
declare @end DATE 

SELECT  @start='2011-05-19' , @end='2011-08-15' 

;with months (date)
AS
(
   SELECT DATEADD(DAY,1,EOMONTH(@start,-1))
    UNION ALL
    SELECT DATEADD(month,1,date)
    from months
    where DATEADD(month,1,date) < EOMONTH(@end)
)
select Datename(month,date)
from months
Community
  • 1
  • 1
gotqn
  • 42,737
  • 46
  • 157
  • 243
1
    declare @start DATE = '2011-05-30'
    declare @end DATE = '2011-06-10' 
   ;with months (date)
    AS
    (
        SELECT @start
        UNION ALL
        SELECT DATEADD(month,1,date)
        from months
        where DATEADD(month,1,date)<= DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,@end)+1,0))
    )
    select Datename(month,date) from months
Sumesh
  • 31
  • 2
0

Well, @bogdhan sahlean has given a nice set based solution, but restricts the values upto 2048 considering the datatype date and datetime2 which the range for year is 0001-01-01 to 9999-12-31, From MSDN

Date range 0001-01-01 through 9999-12-31

January 1,1 CE through December 31, 9999 CE

even though this is the extreme case but worth knowing. Since what if one day someone is trying to project months more than 170 years :)

Even the most upvoted answers are not fulfilling some edge cases (when start date >end date will not show the month of end date, also, the recursive query fails after 100 executions by default). And also using recursive cte for iteration which is performance hog when used massively.

Now , a better solution (IMHO) is to use a calendar table or a tally table to generate the months between two dates. IF one can't create a table , there is better alternative to use Itzik ben Gans cascading CTE for generating numbers table.(here) Which is faster,No logical,physical reads,No worktable NADA

Here is the code

DECLARE @start DATETIME2 = '00010101'
DECLARE @end DATETIME2 = '99991231'
;WITH lv0 AS (SELECT 0 g UNION ALL SELECT 0)
    ,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) -- 4
    ,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv1 b) -- 16
    ,lv3 AS (SELECT 0 g FROM lv2 a CROSS JOIN lv2 b) -- 256
    ,lv4 AS (SELECT 0 g FROM lv3 a CROSS JOIN lv3 b) -- 65,536
    ,lv5 AS (SELECT 0 g FROM lv4 a CROSS JOIN lv4 b) -- 4,294,967,296
    ,Tally (n) AS (SELECT 0 UNION SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM lv5)
SELECT DATENAME(YEAR,DATEADD(MONTH,N,@start)) AS [Year Part], DATENAME(MONTH,DATEADD(MONTH,n,@start)) AS [Month Part]
FROM Tally where N between 0 and DATEDIFF(mm,@start,@end)  
ORDER BY n;

NB: I have added SELECT 0 to start the numbers from 0 th position

The performance shown in my PC is

Itzik Method

(119988 row(s) affected)

SQL Server Execution Times: CPU time = 187 ms, elapsed time = 706 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.

One of the recursive solution given here which takes a time of

(119988 row(s) affected) Table 'Worktable'. Scan count 2, logical reads 719923, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times: CPU time = 890 ms, elapsed time = 1069 ms.

The performance between tally table, calendar table and itzik number table may vary slightly but works like charm with all date range you supply.

Biju jose
  • 263
  • 2
  • 15
0

Create Database function like below

CREATE FUNCTION [dbo].[DateRange] 
(@Identifier CHAR(1),@StartDate DATETIME,@EndDate DATETIME)
RETURNS @SelectedRange TABLE(Dates DATE) AS
 BEGIN

   ;WITH cteRange (DateRange) AS (
   SELECT @StartDate
     UNION ALL
   SELECT
     CASE
         WHEN Upper(@Identifier) = 'H' THEN DATEADD(hh, 1, DateRange)
         WHEN Upper(@Identifier) = 'D' THEN DATEADD(dd, 1, DateRange)
         WHEN Upper(@Identifier) = 'W' THEN DATEADD(ww, 1, DateRange)
         WHEN Upper(@Identifier) = 'M' THEN DATEADD(mm, 1, DateRange)
         WHEN Upper(@Identifier) = 'Y' THEN DATEADD(yy, 1, DateRange)
     END
   FROM cteRange
   WHERE DateRange <=
    CASE
        WHEN Upper(@Identifier) = 'H' THEN DATEADD(hh, -1, @EndDate)
        WHEN Upper(@Identifier) = 'D' THEN DATEADD(dd, -1, @EndDate)
        WHEN Upper(@Identifier) = 'W' THEN DATEADD(ww, -1, @EndDate)
        WHEN Upper(@Identifier) = 'M' THEN DATEADD(mm, -1, @EndDate)
        WHEN Upper(@Identifier) = 'Y' THEN DATEADD(yy, -1, @EndDate)
    END)
 INSERT INTO @SelectedRange (Dates) SELECT DateRange FROM cteRange
  OPTION (MAXRECURSION 3660);
RETURN
END

Then using the function we can generate a range of dates

SELECT * from dbo.DateRange('M','1953-01-01','2019-01-01')

If we want formatted output we can store the result in table variable like below example,

DECLARE @tblDateRange TABLE (AutoID INT IDENTITY(1,1),DateRange DATE)
INSERT INTO @tblDateRange SELECT * from dbo.DateRange('M','1953-01-01','2019-01-01')
SELECT 
LEFT(DATENAME(MONTH,DateRange),3) [MonthYearValue],YEAR(DateRange) AS [Year]
FROM @tblDateRange

Based on our need we can change

OPTION (MAXRECURSION 3660)
Dhruv Patel
  • 106
  • 4
  • Hi, how could I apply this logic to a table (called charges) holding dates where I want to display rows for each month between two dates held in a table. I have a field called start date and one called end date. which would need to replace SELECT * from dbo.DateRange('M','1953-01-01','2019-01-01') these two dates? Thanks. – scw Jul 13 '20 at 09:20
  • yes, Please replace SELECT * from dbo.DateRange('M','1953-01-01','2019-01-01') with dates you want. – Dhruv Patel Sep 16 '20 at 18:25
-1

Try this:

declare 
       @sd date=getdate(),
       @ld date='2016-01-01'

select 
     Datename(month,dateadd(month,number,GETDATE())), 
     number
from master.dbo.spt_values 
where type='p' 
     and dateadd(month,number,GETDATE()) <= @ld
trailmax
  • 34,305
  • 22
  • 140
  • 234
Vikas P
  • 13
  • 3