Calculating the working hours in a given period - sql-server

I'm trying to create a function where I can calculate the total working hours in a given period.
Explanation:
inputs are in mm/dd/yyyy hh:mm format.
For example user Inputs:
startDateTime: 01/10/2013 9:30am endDateTime: 12/12/2013 5pm
Daily working hours from 7am to 5pm. For example, if someone starts working at 9am and ends at 8pm them is working hours will calculate as 8 hours (only calculate the working hours between 7am to 5pm).
I do have a holiday list:
Every week's Saturday and Sunday are holiday
and bellow are the public holidays
HOLIDAY LIST:
07/04/yyyy
12/25/yyyy
12/31/yyyy
Memorial Day:- Last Monday of every May
Labor Day:- First Monday of Every September
Thanksgiving Day:- Fourth Thursday of every November
Now my problem is how to find out Memorial Day, Labor Day and Thanksgiving Day with in any given time period.
Final output will be the total working hours in the given period substituting the hours of holiday.
Bellow function I'm using only to calculate working hours eliminating weekends
CREATE FUNCTION [dbo].[WorkTime]
(
#StartDate DATETIME,
#FinishDate DATETIME
)
RETURNS BIGINT
AS
BEGIN
DECLARE #Temp BIGINT
SET #Temp=0
DECLARE #FirstDay DATE
SET #FirstDay = CONVERT(DATE, #StartDate, 112)
DECLARE #LastDay DATE
SET #LastDay = CONVERT(DATE, #FinishDate, 112)
DECLARE #StartTime TIME
SET #StartTime = CONVERT(TIME, #StartDate)
DECLARE #FinishTime TIME
SET #FinishTime = CONVERT(TIME, #FinishDate)
DECLARE #WorkStart TIME
SET #WorkStart = '07:00'
DECLARE #WorkFinish TIME
SET #WorkFinish = '17:00'
DECLARE #DailyWorkTime BIGINT
SET #DailyWorkTime = DATEDIFF(HOUR, #WorkStart, #WorkFinish)
IF (#StartTime<#WorkStart)
BEGIN
SET #StartTime = #WorkStart
END
IF (#FinishTime>#WorkFinish)
BEGIN
SET #FinishTime=#WorkFinish
END
DECLARE #CurrentDate DATE
SET #CurrentDate = #FirstDay
DECLARE #LastDate DATE
SET #LastDate = #LastDay
WHILE(#CurrentDate<=#LastDate)
BEGIN
IF (DATEPART(dw, #CurrentDate)!=1 AND DATEPART(dw, #CurrentDate)!=7)
BEGIN
IF (#CurrentDate!=#FirstDay) AND (#CurrentDate!=#LastDay)
BEGIN
SET #Temp = #Temp + #DailyWorkTime
END
--IF it starts at startdate and it finishes not this date find diff between work finish and start as minutes
ELSE IF (#CurrentDate=#FirstDay) AND (#CurrentDate!=#LastDay)
BEGIN
SET #Temp = #Temp + DATEDIFF(HOUR, #StartTime, #WorkFinish)
END
ELSE IF (#CurrentDate!=#FirstDay) AND (#CurrentDate=#LastDay)
BEGIN
SET #Temp = #Temp + DATEDIFF(HOUR, #WorkStart, #FinishTime)
END
--IF it starts and finishes in the same date
ELSE IF (#CurrentDate=#FirstDay) AND (#CurrentDate=#LastDay)
BEGIN
SET #Temp = DATEDIFF(HOUR, #StartTime, #FinishTime)
END
END
SET #CurrentDate = DATEADD(day, 1, #CurrentDate)
END
-- Return the result of the function
IF #Temp<0
BEGIN
SET #Temp=0
END
RETURN #Temp
END
Please help me how to find out the Thanksgiving Day, Labor Day and Memorial Day in a given period.

Related

SQL Sever - Datediff, hours, but exclude Saturday/ Sundays

I'm wondering how to calculate the "number of hours" between two timestamps (2016-02-24 17:30:00 and another, for instance) in SQL server- but excluding Saturday and Sunday's full 48 hour period, if crossed.
This isn't quite the same as pure business hours, but sort of. The reason for this is long-winded and unnecessary.
EDIT: I can also say that the end-date will always be during the week. So really ... the "start date" can simply be transmuted to Monday midnight, if on Sat/ Sun ... then maybe a function include the total week count...
DATEDIFF(Week, date, date2) will return the number of week boundaries that are crossed between the two dates. For SQL Server, this means how many Sundays are between the dates (as opposed to the number of 7 day periods are between them). This means, that if you can indeed assume that start and end date will not be a saturday or sunday, you can subtract 48 X DATEDIFF(Week, date, date2) from your normal DATEDIFF call and that should give you what are after.
I would use the below code
declare #NumberOfHours int
declare #StartTime datetime
declare #EndTime datetime
set #StartTime = '2017-02-02 17:30:00.000'
set #EndTime = '2017-02-07 00:00:00.000'
set #NumberOfHours = DATEDIFF(HOUR,#StartTime,#EndTime)
if(datepart(WEEKDAY, #StartTime)=1)
begin
set #NumberOfHours = #NumberOfHours DATEDIFF(HH,#StartTime,#EndTime)%24
end
else if(datepart(WEEKDAY, #StartTime)=7)
begin
set #NumberOfHours = #NumberOfHours - DATEDIFF(HH,#StartTime,#EndTime)%24
set #NumberOfHours = #NumberOfHours - 24
end
else
begin
set #NumberOfHours = #NumberOfHours - datediff(ww,#StartTime,#EndTime)*48
end
print #NumberOfHours
I would use a calendar table (ex. dbo.DateDimension, ref https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/):
CREATE TABLE dbo.DateDimension
(
DateKey INT NOT NULL PRIMARY KEY,
[Date] DATE NOT NULL,
[Day] TINYINT NOT NULL,
DaySuffix CHAR(2) NOT NULL,
[Weekday] TINYINT NOT NULL,
WeekDayName VARCHAR(10) NOT NULL,
IsWeekend BIT NOT NULL,
IsHoliday BIT NOT NULL,
...
)
and, also, following query:
SELECT SUM(
CASE
WHEN dd.[Date] = CONVERT(DATE, #StartDate) THEN DATEDIFF(MINUTE, #StarDate, DATEADD(DAY, 1, dd.[Date]))
WHEN dd.[Date] = CONVERT(DATE, #EndDate) THEN DATEDIFF(MINUTE, dd.[Date], #EndDate)
ELSE 24 * 60 -- Full day
END) / 60 AS SumOfHours
FROM dbo.DateDimension dd
WHERE dd.[Date] >= CONVERT(DATE, #StartDate) AND dd.[Date] <= CONVERT(DATE, #EndDate)
AND dd.IsWeekend = 0
Above query will compute total amount of minutes for requested period of time and then it will divide by 60 to get number of hours.

How to store the result of each iteration of a cycle? (SQL Server)

I'm making a stored procedure, the purpose is to produce a Forecast for maintenance schedule, it so that when I give it a range of dates and amount of days, it would give me a set of results displaying the different dates that fall under that criteria.
I have been trying with a while loop, but I have not been able to get more than 1 result, I only get the last possible result of the range.
ALTER PROCEDURE [dbo].[ApruebaFecha](
-- Add the parameters for the stored procedure here
#LastDate DATETIME, -- Last Scheduled Date (Range Start)
#Setting1 INT, -- Length of Period
#FechaHasta datetime, -- End of Range
#Result DATETIME OUTPUT)
AS
BEGIN
/* Today */
DECLARE
#TodaysDate DATETIME
SELECT #TodaysDate = CONVERT(DATETIME, CONVERT(VARCHAR, GETDATE(), 101), 101)
SELECT convert(datetime, CONVERT(varchar,#FechaHasta,101),101 )
-- Finding today's date after resetting the time to midnight
/* Schedule Date */
DECLARE
#ScheduleDate DATETIME
-- Find the starting schedule date. If the schedule date is in a previous
-- month, adjust to the current month
-- Adjust the days
SELECT #ScheduleDate = #LastDate
WHILE (#ScheduleDate < #TodaysDate AND #TodaysDate <#FechaHasta)
SELECT #ScheduleDate = DATEADD(day, #Setting1, #ScheduleDate)
IF (#ScheduleDate = #LastDate)
SELECT #ScheduleDate = DATEADD(day, #Setting1, #ScheduleDate)
SELECT #Result = #ScheduleDate
WHILE(#Result <#FechaHasta)
BEGIN
IF(#Result <#FechaHasta)
SELECT #Result = DATEADD(day, #Setting1, #Result)
SET IDENTITY_INSERT [00TblFecha] ON
INSERT INTO dbo.[00TblFecha](idFecha,jobno,fecha)VALUES('','',#Result)
SET IDENTITY_INSERT[00TblFecha] OFF
print #Result
end
END -- GetScheduleDate_Daily_PeriodicDay
the result that you see is from this statement at line 5:
SELECT convert(datetime, CONVERT(varchar,#FechaHasta,101),101 )
you should put a statement at the end of your stored procedure like this :
select idFecha,jobno,fecha
from dbo.[00TblFecha]

Creating a time dimension with specific dates on SQL

I'm making an analysis project on football transfers. I have a model with one Fact Table called FactTransfers and I need to link it to a time dimension but i need a specific range of dates, namely the dates where it's possible to transfer players (from June 1st to September 1st and from January 1st to January 31st).
I have seen some posts related to the matter but they all have code and attributes that I don't need.
Basically what i want is:
Date as primary key,
Day of the month,
Name of the month,
Transfer window (summer or winter),
Year.
I'm not too familiarized with sql code and I have spent hours trying to figure it out without the results I need.
Thank you in advance for all your help!
Here is the code to create and populate your Dim table for Dates. Hope this helps.
CREATE TABLE [dbo].[DimDate]
(
[DateKey] INT primary key,
[Date] DATETIME,
[DayofMonth] TINYINT, -- Field will hold day number of Month
[NameofMonth] VARCHAR(9),--January, February etc
[TransferWindow] VARCHAR(20), -- Summer & Winter
)
--Specify Start Date and End date here
--Value of Start Date Must be Less than Your End Date
DECLARE #StartDate DATETIME = '01/01/2015' --Starting value of Date Range
DECLARE #EndDate DATETIME = '12/31/2025' --End Value of Date Range
DECLARE #CurrentDate AS DATETIME = #StartDate
WHILE #CurrentDate < #EndDate
BEGIN
INSERT INTO [dbo].[DimDate]
SELECT
CONVERT (char(8),#CurrentDate,112) as DateKey,
#CurrentDate AS Date,
DATEPART(DD, #CurrentDate) AS [DayOfMonth],
DATENAME(MM, #CurrentDate) AS [MonthName],
CASE WHEN (MONTH(#CurrentDate) BETWEEN 6 AND 8) OR ( MONTH(#CurrentDate) =9 AND DATEPART(DD, #CurrentDate)=1) THEN 'Summer'
WHEN MONTH(#CurrentDate) =1 THEN 'Winter'
ELSE ''
END AS [TransferWindow]
SET #CurrentDate = DATEADD(DD, 1, #CurrentDate)
END
SELECT * FROM [DimDate]
--DROP TABLE [DimDate]

Need function the gives DateTime for first day of week given the day number for first day of week

For many, first day of work week is Monday, but lets say its a different day, say Wednesday.
Can you help me create a function (SQL Server 2012) that returns the date for the fist day in:
Current Week
Next Week
Last Week
So a function where we give a date, Type(Current, Next, Last), FirstDay(0,1,2,3,4,5,6) and would return a datetime of first day of Current or next or Last day of week depending on type.
CREATE FUNCTION [dbo].[GetStartofWeek]
(
#FirstDay int,
#type VARCHAR(10),
#day DATETime
)
RETURNS DATE
AS
BEGIN
Thanks
I believe this will give you the dates you are looking for:
create function [dbo].[GetStartofWeek]
(
#firstDay int,
#type varchar(10),
#day date
)
returns date as
begin
-- use datediff/dateadd to get the date of sunday for the week of the given #day (-1 casts as Sunday 1899-12-31)
declare #sundayOfWeek date = dateadd(week, datediff(week, -1, #day), -1)
-- #firstDay is 0 to 6, 0 representing Sunday
declare #firstDayOfWeek date = dateadd(day, #firstDay, #sundayOfWeek)
-- add or subtract a week if necessary
set #type = upper(#type)
if #type = 'LAST'
set #firstDayOfWeek = dateadd(week, -1, #firstDayOfWeek)
else if #type = 'NEXT'
set #firstDayOfWeek = dateadd(week, 1, #firstDayOfWeek)
return #firstDayOfWeek
end
go
Tested with the following inputs:
select [dbo].[GetStartofWeek](0, 'Last', '2014-12-14') -- 2014-12-07
select [dbo].[GetStartofWeek](3, 'Last', '2014-12-14') -- 2014-12-10
select [dbo].[GetStartofWeek](6, 'Last', '2014-12-14') -- 2014-12-13
select [dbo].[GetStartofWeek](0, 'Current', '2014-12-17') -- 2014-12-14
select [dbo].[GetStartofWeek](3, 'Current', '2014-12-17') -- 2014-12-17
select [dbo].[GetStartofWeek](6, 'Current', '2014-12-17') -- 2014-12-20
select [dbo].[GetStartofWeek](0, 'Next', '2014-12-20') -- 2014-12-21
select [dbo].[GetStartofWeek](3, 'Next', '2014-12-20') -- 2014-12-24
select [dbo].[GetStartofWeek](6, 'Next', '2014-12-20') -- 2014-12-27
One suggestion would be to use a week offset instead of 'Last'/'Current'/'Next', which is a bit cleaner and more flexible:
alter function [dbo].[GetStartofWeek]
(
#firstDay int,
#weekOffset int,
#day date
)
returns date as
begin
-- use datediff/dateadd to get the date of sunday for the week of the given #day (-1 casts as Sunday 1899-12-31)
declare #sundayOfWeek date = dateadd(week, datediff(week, -1, #day), -1)
-- #firstDay is 0 to 6, 0 representing Sunday
declare #firstDayOfWeek date = dateadd(day, #firstDay, #sundayOfWeek)
-- add or subtract weeks if necessary
if #weekOffset <> 0
begin
set #firstDayOfWeek = dateadd(week, #weekOffset, #firstDayOfWeek)
end
return #firstDayOfWeek
end
go

SQL Server Check if day does not fall on weekend and if so, iterate to a weekday

I am using SQL Server with t-Sql
I have the following code that checks to see if a date falls on a weekend
and if it does, it will iterate until the day falls on a weekday
Declare #ProDate as Date
set #ProDate = '08/05/12'
WHILE (DATEPART(DW, #ProDate) = 1 OR DATEPART(DW, #ProDate) = 7 )
BEGIN
set #ProDate = DATEADD(day, 1, #ProDate)
END
select #ProDate
The code seems to work. Wondering if I missed anything or if there is a better way to handle this.
This code is dependent on the setting of DATEFIRST in your system.
I'd add a SET DATEFIRST 7 before the date checks
Alternately, this avoids the while loop
declare #df int = ##Datefirst
set datefirst 1
select
case when DATEPART(DW, #ProDate)>=6 then
DATEADD(d, 8-DATEPART(DW, #ProDate), #prodate)
else #ProDate
end
set DATEFIRST #df
This code will work. It is almost identical to code that we use in a heavily used function.
The only suggestion that I might have is do you need to integrate a Holiday check? We have a Holiday table to store dates that need to be skipped as well.
Use below code to get next wrking date after excluding weekends and Holidays
Declare #AddDay as integer = 3
Declare #NextWorkingDate DateTime
Declare #StartDate DateTime = Cast(getdate() as date)
While #AddDay > 0
begin
Select #NextWorkingDate = #StartDate + #AddDay +
(datediff(wk, #StartDate, #StartDate+ #AddDay ) * 2) -- add weekend
--Exclude weekend
If datepart(dw,#NextWorkingDate ) = 1 or datepart(dw,#NextWorkingDate ) = 7 --Add 2 days if target date is either Saturday or Sunday
set #NextWorkingDate = #NextWorkingDate + 2
--Count no of holidays if falling within start date and nextwrking date
Select #AddDay = Count(*) from HolidayTable ST --Holiday list
where ST.OffDate between #StartDate+1 and #NextWorkingDate
Set #StartDate = #NextWorkingDate
End
Select #NextWorkingDate
USE below to exclude weekeends and Holiday
Declare #AddDay as integer = 3
Declare #NextWorkingDate DateTime
Declare #StartDate DateTime = Cast(getdate() as date)
While #AddDay > 0
begin
Select #NextWorkingDate = #StartDate + #AddDay +
(datediff(wk, #StartDate, #StartDate+ #AddDay ) * 2) -- add weekend
--Exclude weekend
If datepart(dw,#NextWorkingDate ) = 1 or datepart(dw,#NextWorkingDate ) = 7 --Add 2 days if target date is either Saturday or Sunday
set #NextWorkingDate = #NextWorkingDate + 2
--Count no of holidays if falling within Hold days/Deposit days
Select #AddDay = Count(*) from HolidayTable ST --Holiday list
where ST.OffDate between #StartDate+1 and #NextWorkingDate
Set #StartDate = #NextWorkingDate
End
Select #NextWorkingDate

Resources