Reputation: 675
I have a query (below) that will start at a specific time. from that time i need to increment a week at a time (each new row will be a new week - going from sunday to saturday) i have this done - but the new wrinkle is that if it is the end of the month it needs to stop on that date and start up on the first of the month but still stop on that saturday. result set good/bad are listed below and also my query up to this point.
Bad results:
2016-05-22 2016-05-28
2016-05-29 2016-06-04
What I need:
2016-05-22 2016-05-28
2016-05-29 2016-05-31
2016-06-01 2016-06-04
Code:
BEGIN
DECLARE @StartDate DATE = DATEFROMPARTS(2016, 5, 22);
DECLARE @EndDate DATE = CAST(GETDATE() AS DATE);
DECLARE @Today DATE = @EndDate;
DECLARE @EndOfMonth DATE = @StartDate
; WITH [Dates] AS (
SELECT
@StartDate AS [StartDate],
DATEADD(DAY, 6, @StartDate) AS [EndDate]
UNION ALL
SELECT
DATEADD(DAY, 7, [StartDate]),
DATEADD(DAY, 7, [EndDate])
FROM [Dates]
WHERE DATEADD(DAY, 7, [StartDate]) <= @EndDate
)
SELECT
[tcw].[StartDate],
[tcw].[EndDate]
FROM [Dates] AS [tcw]
OPTION (MAXRECURSION 0)
END
GO
Upvotes: 1
Views: 280
Reputation: 40359
Given that you use DATEFROMPARTS
, I'm assuming you are using SQL 2012 or later. I'm also relying on your existing logic with regards to determining the first and last dates. Based on this, replace your query with the following:
; WITH [Dates] AS (
SELECT
@StartDate AS [StartDate],
DATEADD(DAY, 6, @StartDate) AS [EndDate]
UNION ALL
SELECT
DATEADD(DAY, 7, [StartDate]),
DATEADD(DAY, 7, [EndDate])
FROM [Dates]
WHERE DATEADD(DAY, 7, [StartDate]) <= @EndDate
)
-- All weeks where all dates are within the same month
SELECT
StartDate
,EndDate
FROM [Dates]
WHERE MONTH(StartDate) = MONTH(EndDate)
-- For weeks where all dates not within the same month, the last week in the month
UNION ALL SELECT
StartDate
,EOMONTH(StartDate)
FROM [Dates]
WHERE MONTH(StartDate) <> MONTH(EndDate)
-- For weeks where all dates not within the same month, the first week in the (next) month
UNION ALL SELECT
DATEADD(dd, 1, EOMONTH(StartDate))
,EndDate
FROM [Dates]
WHERE MONTH(StartDate) <> MONTH(EndDate)
The unions make it a bit awkward, but unless you're processing centuries with every pass it will run quickly enough. Note also that some "weeks" will contain one day, such as for July 31, 2016 through July 31, 2016.
-- Addenda, based on the comment ---------------------------------
The following query does this, but with a big warning…
; WITH cteDates AS (
SELECT
@StartDate AS StartDate,
DATEADD(DAY, 6, @StartDate) AS EndDate,
CASE
WHEN DATEPART(dw, EOMONTH(@StartDate)) between 2 and 6 then 0 -- Assumes SET DATEFIRST is 1!
ELSE 1
END AS MonthEndsOnWeekend
UNION ALL
SELECT
DATEADD(DAY, 7, StartDate),
DATEADD(DAY, 7, EndDate),
CASE
WHEN DATEPART(dw, EOMONTH(DATEADD(DAY, 7, StartDate))) between 2 and 6 then 0
ELSE 1
END
FROM cteDates
WHERE DATEADD(DAY, 7, StartDate) <= @EndDate
)
-- All weeks where all dates are within the same month,
-- and all month-ending weeks where the last day of the month is Saturday or Sunday
SELECT
StartDate
,EndDate
FROM cteDates
WHERE MONTH(StartDate) = MONTH(EndDate)
OR MonthEndsOnWeekend = 1
-- For weeks where all dates not within the same month,
-- and the the last day of the month is NOT Saturday or Sunday,
-- the last week in the month
UNION ALL SELECT
StartDate
,EOMONTH(StartDate)
FROM cteDates
WHERE MONTH(StartDate) <> MONTH(EndDate)
AND MonthEndsOnWeekend = 0
-- For weeks where all dates not within the same month,
-- and the the last day of the month is NOT Saturday or Sunday,
-- the first week in the (next) month
UNION ALL SELECT
DATEADD(dd, 1, EOMONTH(StartDate))
,EndDate
FROM cteDates
WHERE MONTH(StartDate) <> MONTH(EndDate)
AND MonthEndsOnWeekend = 0
I use the DATEPART
function to identify the day of week (Saturday, Sunday, etc). SQL will return a number for this function, where the number returned for day of week depends upon the setting of SET DATEFIRST. For my installations, we use the default where 1 = Monday. If you do not have complete control over the setting of SET DATEFIRST everywhere your code may ever be run, forever, then you may have problems outside the scope of this discussion. An alternative that I have used is to use DATENAME
, which will return character strings, e.g. Saturday, Sunday, etc… but this has the same problem with regards to the setting of SET LANGUAGE.
(fyi, I also took out the [ ] and renamed the cte, because they were bugging me.)
Upvotes: 1
Reputation: 1325
Split any weeks that breach end of month into two, then union back them into that are not breaching the end of month.
declare
@StartDate date = datefromparts(2016, 5, 22),
@EndDate date = cast(getdate() as date)
;
with
dates as
(
select
@StartDate as StartDate,
dateadd(day, 6, @StartDate) as EndDate,
eomonth(@StartDate) as EndOfMonth,
dateadd(day, 1 - day(dateadd(month, 1, @StartDate)),
dateadd(month, 1, @StartDate)) as FirstDayOfNextMonth
union all
select
dateadd(day, 7, d.StartDate),
dateadd(day, 7, d.EndDate),
eomonth(dateadd(day, 7, d.StartDate)),
dateadd(day, 1 - day(dateadd(month, 1, d.StartDate)),
dateadd(month, 1, d.StartDate))
from
dates as d
where
dateadd(day, 7, StartDate) <= @EndDate
)
-- week within the month
select
d.StartDate,
d.EndDate
from
dates as d
where
d.EndDate <= d.EndOfMonth
union all
-- week breach to next month, first part
select
d.StartDate,
d.EndOfMonth as EndDate
from
dates as d
where
d.EndDate > d.EndOfMonth
union all
-- week breach to next month, second part
select
d.FirstDayOfNextMonth as StartDate,
d.EndDate
from
dates as d
where
d.EndDate > d.EndOfMonth
order by 1
option (maxrecursion 0)
Result:
| StartDate | EndDate |
|------------|------------|
| 2016-05-22 | 2016-05-28 |
| 2016-05-29 | 2016-05-31 |
| 2016-06-01 | 2016-06-04 |
| 2016-06-05 | 2016-06-11 |
| 2016-06-12 | 2016-06-18 |
| 2016-06-19 | 2016-06-25 |
| 2016-06-26 | 2016-06-30 |
| 2016-07-01 | 2016-07-02 |
| 2016-07-03 | 2016-07-09 |
| 2016-07-10 | 2016-07-16 |
| 2016-07-17 | 2016-07-23 |
| 2016-07-24 | 2016-07-30 |
| 2016-07-31 | 2016-07-31 |
| 2016-08-01 | 2016-08-06 |
| 2016-08-07 | 2016-08-13 |
| 2016-08-14 | 2016-08-20 |
Upvotes: 0
Reputation: 82020
I added the Day of Week (beg/end) just to illustrate
Declare @Date1 Date='2016-05-22'
Declare @Date2 Date='2016-07-31'
;with cteBase as (
Select *
,Flag1=IIF(Row_Number() over (Partition By Month(RetVal) Order By RetVal)=1 or DatePart(WEEKDAY,RetVal)=1 or Day(RetVal)=1 ,1,0)
,Flag2=IIF(DatePart(WEEKDAY,RetVal)=7 or Lead(Day(RetVal),1) over (Order By RetVal)=1,1,0)
,RowNr=Row_Number() over (Order By RetVal)
From [dbo].[udf-Create-Range-Date](@Date1,@Date2,'DD',1)
)
Select DateR1=cast(A.RetVal as Date)
,B.DateR2
,DOW1=DateName(Weekday,A.RetVal)
,DOW2=DateName(Weekday,B.DateR2)
From cteBase A
Cross Apply (Select DateR2=min(cast(RetVal as date)) From cteBase Where Flag2=1 and RowNr>=A.RowNr) B
Where A.Flag1=1 and B.DateR2 is not null
Returns
DateR1 DateR2 DOW1 DOW2
2016-05-22 2016-05-28 Sunday Saturday
2016-05-29 2016-05-31 Sunday Tuesday
2016-06-01 2016-06-04 Wednesday Saturday
2016-06-05 2016-06-11 Sunday Saturday
2016-06-12 2016-06-18 Sunday Saturday
2016-06-19 2016-06-25 Sunday Saturday
2016-06-26 2016-06-30 Sunday Thursday
2016-07-01 2016-07-02 Friday Saturday
2016-07-03 2016-07-09 Sunday Saturday
2016-07-10 2016-07-16 Sunday Saturday
2016-07-17 2016-07-23 Sunday Saturday
2016-07-24 2016-07-30 Sunday Saturday
The UDF I use to create dynamic date Ranges
CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int)
Returns
@ReturnVal Table (RetVal datetime)
As
Begin
With DateTable As (
Select DateFrom = @DateFrom
Union All
Select Case @DatePart
When 'YY' then DateAdd(YY, @Incr, df.dateFrom)
When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom)
When 'MM' then DateAdd(MM, @Incr, df.dateFrom)
When 'WK' then DateAdd(WK, @Incr, df.dateFrom)
When 'DD' then DateAdd(DD, @Incr, df.dateFrom)
When 'HH' then DateAdd(HH, @Incr, df.dateFrom)
When 'MI' then DateAdd(MI, @Incr, df.dateFrom)
When 'SS' then DateAdd(SS, @Incr, df.dateFrom)
End
From DateTable DF
Where DF.DateFrom < @DateTo
)
Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)
Return
End
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15)
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1)
Upvotes: 0
Reputation: 14381
Looking at your code I assume you are using sql-server. EOMONTH() is available in sql-server 2012 + and makes finding the end date a little easier. Basically you need to test for what day of the week the start date is and how close to the end of the month a start day is and then balance that through your recursion. you can add another LEVEL column and track a week number if you want. E.g. 1 as LEVEL in first query in UNION Level+1 as Level.
DECLARE @StartDate DATE = '2016-05-22'
DECLARE @EndDate DATE = GETDATE()
--if @StartDate provided is middle of the week and you want to adjust to sunday use this
SET @StartDate = CASE
WHEN DATEPART(WEEKDAY,@StartDate) = 1 THEN @StartDate
ELSE DATEADD(day,-7 + DATEPART(WEEKDAY,@StartDate),@StartDate)
END
;WITH cteRecursiveDates AS (
SELECT
@StartDate as Startdate
,CASE
WHEN DATEDIFF(day,@StartDate,EOMONTH(@StartDate)) < (7 - DATEPART(WEEKDAY,@StartDate))
THEN DATEADD(day,DATEDIFF(day,@StartDate,EOMONTH(@StartDate)) ,@StartDate)
ELSE DATEADD(day,(7- DATEPART(WEEKDAY,@StartDate)),@StartDate)
END as EndDate
UNION ALL
SELECT
DATEADD(day,1,c.EndDate) as StartDate
,CASE
WHEN DATEDIFF(day,DATEADD(day,1,c.EndDate),EOMONTH(DATEADD(day,1,c.EndDate))) < (7 - DATEPART(WEEKDAY,DATEADD(day,1,c.EndDate)))
THEN DATEADD(day,DATEDIFF(day,DATEADD(day,1,c.EndDate),EOMONTH(DATEADD(day,1,c.EndDate))) ,DATEADD(day,1,c.EndDate))
ELSE DATEADD(day,(7- DATEPART(WEEKDAY,DATEADD(day,1,c.EndDate))),DATEADD(day,1,c.EndDate))
END as EndDate
FROM
cteRecursiveDates c
WHERE
DATEADD(day,1,c.EndDate) <= @EndDate
)
SELECT *
FROM
cteRecursiveDates
Upvotes: 0