get first and last date of 4-week period - sql-server

I am trying to create a calender table that contains indivudual dates.
For each date a column is present that tells you in which week, isowk, month, quarter and so on thast date belongs.
That is not a big problem. But now we are working with 4-week periods, based on isowk.
So
date 2014-12-30 belongs to period 1 which starts on 2014-12-30 and ends on 2014-01-26
date 2014-02-01 belongs to period 2 which starts on 2014-01-27 and ends on 2014-03-02
date 2014-08-05 belongs to period 8 which starts on 2014-07-14 and ends on 2014-08-11
It is easy to calculate which period a date belongs to:
period_number = ceiling(cast(datepart(isowk,#date) as float) /4 )
Finding the start and end of that particular week is not that hard either:
start_week = cast(DATEADD(wk,DATEDIFF(wk,0,#date),0) as date)
end_week = cast(DATEADD(wk,DATEDIFF(wk,0,#date),6) as date)
but how do I calculate the first date of that 4-week period?
thanks for thinking with me

If the start week/end week logic works, then this might work:
start_week = cast(DATEADD(wk, 4 * (DATEDIFF(wk, 0, #date) / 4), 0) as date)
end_week = cast(DATEADD(wk, 4 * (DATEDIFF(wk, 0, #date) / 4), 27) as date)
I'm not sure why you are using wk instead of isowk if you want ISO weeks.

Related

Calculate working hours between two dates based on business hours

I have a table in SQL Server representing service requests with their created date and LastUpdateAt which represents the end date if exist, if null it means the request is open and not yet solved.
I want to calculate the working hours for each request to be closed or if it's not yet closed, then the hours from the created date until current time, with weekends excluded and only calculating working hours each working day, from 8am to 4pm "8 hours per working day".
What I have tried so far:
WITH CTE AS
(
SELECT
Id,
CreatedAt AS DYN_DATE,
CreatedAt AS START_DATE,
(CASE
WHEN LastUpdateAt IS NULL
THEN GETDATE()
ELSE LastUpdateAt
END) AS END_DATE
FROM
Request
WHERE
Id IN ('14578')
)
SELECT
(DATEDIFF(HOUR, START_DATE, END_DATE) - (DATEDIFF(DAY, START_DATE, END_DATE) * 16 )) -
((SELECT COUNT(*) FROM CTE
WHERE DYN_DATE BETWEEN START_DATE AND END_DATE
AND DATEPART(WEEKDAY, DYN_DATE) NOT IN (6, 7)) * 24) AS B_DURATION
FROM
CTE
In this query I am trying to calculate the working hours for the request ID : 14578.
In the last part of the query, I was trying to calculate the total hours then subtract the number of non-working hours then also subtract the number of (weekends * 24).
Probably my method is wrong from the beginning, or I am thinking inside the box, so I am open to any suggestion or solution.
There are multiple phases to this calculation. It may be helpful to look at a calendar for the explanation.
Weeks between start and end
DATEDIFF(WEEK, cte2.START_DATE, cte2.END_DATE) As NumWeeks
This is calculating the difference between the rows of a calendar, a week. (a 7 day period generally represented as a single row on a calendar). Saturday and the following Sunday may only be one day apart, but they are in separate weeks, and therefore are in separate rows on the calendar.
Week Days between start and end.
DATEPART(WEEKDAY, cte2.END_DATE) - DATEPART(WEEKDAY, cte2.START_DATE) as NumDays
Step 1 calculated the difference in rows of the calendar, step 2 we're calculating the difference in columns. This will account for partial week differences.
Tuesday to the following Monday is six days, one day less than a week. Step one returned 1 week. Since we're shifting one column to the left, we adjusting the week by -1 days. If the end date had been Wednesday, it would be 1 week plus 1 day, but since it is Monday, it is 1 week minus 1 day.
Normalizing the Start/End time outside of working hours
CAST(CASE WHEN CAST(CTE.START_DATE AS TIME) < '08:00' THEN '08:00'
WHEN CAST(CTE.START_DATE AS TIME) > '16:00' THEN '16:00'
ELSE CAST(CTE.START_DATE AS TIME)
END AS DATETIME) as StartTimeOnly,
CAST(CASE WHEN CAST(CTE.END_DATE AS TIME) < '08:00' THEN '08:00'
WHEN CAST(CTE.END_DATE AS TIME) > '16:00' THEN '16:00'
ELSE CAST(CTE.END_DATE AS TIME)
END AS DATETIME) as EndTimeOnly
This calculation is only interested in the time, so we cast to Time, then back to DateTime. This sets the Date component for both to 1900-01-01.
Similar to the week & day relationship, an End Time that occurs the next day, but before the Start Time will subtract hours credited from the number of days. For example 1/2 at 12:00 to 1/3 at 10:00 would be 1 day(1/3 - 1/2), or 8 hours, - 2 hours (10-12) = 6 hours of business time.
Calculating the difference in minutes, to ensure partial hours are considered correctly, then converting to hours. This ensures times only a couple minutes apart across an hour boundary don't get counted as a full hour. Of course, the trade off is 59 minutes rounds down to 0 hours.
DATEDIFF(MINUTE, StartTimeOnly, EndTimeOnly)/60 as NumHours
If rounding at the half hour...
CAST(ROUND(DATEDIFF(MINUTE, StartTimeOnly, EndTimeOnly) / 60.0, 0) AS INT) AS RoundedHours
Wrap it all together
,
cte2 AS
(
SELECT CTE.START_DATE,
CTE.END_DATE,
StartTimeOnly = CAST(CASE WHEN CAST(CTE.START_DATE AS TIME) < '08:00' THEN '08:00'
WHEN CAST(CTE.START_DATE AS TIME) > '16:00' THEN '16:00'
ELSE CAST(CTE.START_DATE AS TIME)
END AS DATETIME),
EndTimeOnly = CAST(CASE WHEN CAST(CTE.END_DATE AS TIME) < '08:00' THEN '08:00'
WHEN CAST(CTE.END_DATE AS TIME) > '16:00' THEN '16:00'
ELSE CAST(CTE.END_DATE AS TIME)
END AS DATETIME)
FROM CTE
),
CTE3 AS
(
SELECT START_DATE = CAST(cte2.START_DATE AS DATETIME2(0)),
END_DATE = CAST(cte2.END_DATE AS DATETIME2(0)),
NumWeeks = DATEDIFF(WEEK, cte2.START_DATE, cte2.END_DATE),
NumDays = DATEPART(WEEKDAY, cte2.END_DATE) - DATEPART(WEEKDAY, cte2.START_DATE),
NumHours = DATEDIFF(MINUTE, cte2.StartTimeOnly, cte2.EndTimeOnly)/60
FROM cte2
)
SELECT CTE3.START_DATE,
CTE3.END_DATE,
CTE3.NumWeeks,
CTE3.NumDays,
CTE3.NumHours,
TotalBusinessHours = (CTE3.NumWeeks * 5 * 8) + (CTE3.NumDays * 8) + (CTE3.NumHours )
FROM CTE3
;
For more accurate results, you'll also want to add a table containing your holidays. You'll then subtract the number of holidays found between your start and end date from your total number of days, before converting it to hours.
A question you may still need to answer... what happens if the start and end dates occur during non-working hours? e.g. Start at 19:00 and finish at 20:00. Is that 0 business hours to resolve?

Getting last 6 months data from week column

There is an int field say WeekNum and shows data like 202113, 202120.
And I want to get the for last 6 months.
If I subtract (weekid-26), then it will not give correct result.
Can there be a better way to get the result. Data in the current week can be anything like 202101 201952, etc.
DateAdd( month, -6) is not working here.
Sample data is:
WeekID
202110
202109
202108
202111
And the expected result is
WeekID:
201936
201935
201934
201935
You should always store dates in date or datetime fields, otherwise you run into problems such as you have
You need to divide by 100 to get the year, and modulo by 100 to get the week (unclear if it's week or isoweek)
SELECT *
FROM table1
WHERE DATEADD(week, WeekNum % 100, DATEFROMPARTS(WeekNum / 100, 1, 1)) >=
DATEADD(month, -6, GETDATE())

How to find the past 4 weeks of the same weekday value starting today

I am trying to select records from today and the same day of each week for the last 4 weeks.
Today (Tuesday)
Last Tuesday
The Tuesday before that
The Tuesday before that
I need this to be tied to current date because I am going to run this query every day so I don't want to use a between or something where I manually specify the date range.
Everything I have found or tried so far has pulled the last month of data but not the last 4 weeks of the same weekday.
select *
from table
where thedatecolumn >= DATEADD(mm, -1, GETDATE())
This works but pulls everything from the last month.
If today's date is 7/10/2019 I need
Data from 7/10/2019
Data from 7/3/2019
Data from 6/26/2019
Data from 6/19/2019
Every day I will run this query, so I need it to be dynamic based on the current date.
I believe you want to look back 21 days and then filter those dates that have the same day of week:
select * from table
where thedatecolumn >= DATEADD(DAY, -21, CAST(GETDATE() AS DATE))
and DATEPART(WEEKDAY, thedatecolumn) = DATEPART(WEEKDAY, GETDATE())
You Can try using a recursive cte which starts today and repeatedly substracts 7 days - so you ensure you always land on the same weekday. Following an example:
WITH cteFromToday AS(
SELECT 0 AS WeeksBack, GETDATE() AS MyDate
UNION ALL
SELECT WeeksBack + 1 AS WeeksBack, DATEADD(d, -7, MyDate) AS MyDate
FROM cteFromToday
)
SELECT TOP 5 *
FROM cteFromToday
OPTION ( MaxRecursion 0 );
This is quite simple. Substitute CURRENT_TIMESTAMP here for any given date.
SELECT CONVERT(DATE,CURRENT_TIMESTAMP) AS Today,
DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)) AS LastWeek ,
DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)) AS TwoWeeksAgo,
DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP)) AS ThreeWeeksAgo
SO, if you want to get data for a set of ranges for one entire day with those dates:
SELECT something
WHERE
datetimecolumn >= CONVERT(DATE,CURRENT_TIMESTAMP) AND datetimecolumn < DATEADD(DAY,1, CONVERT(DATE,CURRENT_TIMESTAMP)) -- Todays range,
OR datetimecolumn >= DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1,DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)))-- LastWeek ,
OR datetimecolumn >= DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1,DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)))-- TwoWeeksAgo,
OR datetimecolumn >= DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1, DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP))) -- ThreeWeeksAgo

SQL Server Parameter Date Range Issues

I am using the following query to get the difference between two dates. The date ranges are tolling 12 months interval.
CY stand for Current year while PY stands for Previous Year. The dates in Current year are used to calculate the previous year dates
When I execute my query I have the following output, where the month is 11 and day 364. But I want my months to be twelve and the day 365 or (366 for leap year).
DECLARE #CY_StartDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-13, 0) AS DATE),
#CY_EndDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) AS DATE); --- Rolling 12 months
DECLARE #PY_startDate date =DATEADD(YEAR,-1,#CY_StartDate),
#PY_EndDate date =DATEADD(YEAR,-1,#CY_EndDate)
SELECT
#CY_StartDate AS CY_Start,
#CY_EndDate AS CY_End,
#PY_StartDate AS PY_Start,
#PY_EndDate AS PY_End,
DATEDIFF(year, #CY_StartDate, #CY_EndDate) AS yr,
DATEDIFF(month, #CY_StartDate, #CY_EndDate) AS month,
DATEDIFF(day, #CY_StartDate, #CY_EndDate) AS day
Current Output
CY_Start CY_End PY_Start PY_End yr month day
2017-10-01 2018-09-30 2016-10-01 2017-09-30 1 11 364
Expected output
CY_Start CY_End PY_Start PY_End yr month day
2017-10-01 2018-09-30 2016-10-01 2017-09-30 1 12 365
The values you are getting make sense. DATEDIFF counts the ticks between 2 dates, where a tick is the value of the first parameter. So, for example: DATEDIFF(MONTH, '20180101','20180228') will return 1, as only 1 tick has occured (2 - 1 = 1). Seems, here, you simply need to add 1:
DECLARE #CY_StartDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-13, 0) AS DATE),
#CY_EndDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) AS DATE); --- Rolling 12 months
DECLARE #PY_startDate date =DATEADD(YEAR,-1,#CY_StartDate),
#PY_EndDate date =DATEADD(YEAR,-1,#CY_EndDate)
select
#CY_StartDate as CY_Start,
#CY_EndDate AS CY_End,
#PY_StartDate AS PY_Start,
#PY_EndDate AS PY_End,
DATEDIFF(year,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as yr,
DATEDIFF(month,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as month,
DATEDIFF(day,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as day
The reason I used a further DATEADD is because this makes it consistent with every expression. The value of yr was correct, however, for dates like 20170101 and 20171231, the value of yr would be 0. Hence adding 1 day the the value of #CY_EndDate makes this far more reliable, should the dates move.
Common sense. How many numbers are there between 1 and 10 including both? You might say that there are 10 - 1 = 9 which is incorrect. The correct answer is (10 - 1) + 1 = 10.
Likewise, if you have two inclusive dates e.g. 2017-10-01 and 2018-09-30 you add one to DATEDIFF(DAY, '2017-10-01', '2018-09-30') to get 365 instead of 364.
However, as suggested in the other answer, it is much better to the end date exclusive (not counted) which makes date calculations straight forward. In your example, you should add 1 day to the last date so that you have [2017-10-01, 2018-10-01) and DATEDIFF will produce desired results.

Decision based on what day of the week a date falls on

For any given date I need to determine what day of the week it is.
I already know how to get the day of the week with this DATENAME(dw,MyDate.Field)and the number of the day with this DATEPART(dw,MyDate.Field).
Once I've got the day of the week if it is before Wednesday I want to return that Wednesday's date.
If the day of the week is Wednesday or after then I want to return next Wednesdays date.
Monday is day 1 in my system.
Use DATEPART() to determine day of the week.
Use CASE() for the different cases.
Calculate the day delta. Hint: It's either 3-dw or 7+3-dw.
Use DATEADD() to get from the current day (returned by DATEPART) to Wednesday or Wednesday of next week.
the easiest way, without any calculating:
set datefirst 1;
with dates as (
select CAST('20170906' as datetime) d
union all
select DATEADD(day, 1, dates.d)
from dates
where DATEADD(day, 1, dates.d) <= '20170930'
)
select dates.d, DATEADD(day, v.valueToAdd, dates.d) nextWed
from dates
join (values(1,2),(2,1),(3,7),(4,6),(5,5),(6,4),(7,3))v(dayOfWeek,valueToAdd) on v.dayOfWeek = DATEPART(weekday, dates.d)
order by 1

Resources