I have a function that calculate the number of working days between 2 dates. What I want is to convert this existing function into something like it will calculate the number of holidays in month or let's say in between 2 dates.
My existing function is something like this.
CREATE FUNCTION [dbo].[fnGetDaysWorked] (#StartDate datetime, #EndDate datetime)
RETURNS int
AS
BEGIN
DECLARE #dateFrom datetime = '2018/01/01'
DECLARE #dateTo datetime = '2018/01/31'
SET #StartDate = #dateFrom
SET #EndDate = #dateTo
DECLARE #WORKDAYS INT
SELECT #WORKDAYS = (DATEDIFF(dd, #StartDate, #EndDate) + 1)
-(DATEDIFF(wk, #StartDate, #EndDate) * 2)
-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END)
RETURN #WORKDAYS
END
One more thing, I am unable to handle the situation in which there is some sort of public holiday in the month like Independence day or something. I have a table named calendar that handles the situation but I'm not able to integrate that here. Table structure is provided below.
CREATE TABLE Calendar
(
ID Int,
[DayName] Varchar(25),
Holiday datetime,
PublicHoliday Binary,
OffDay Binary,
Active Binary
);
Any help would be appreciated.
This will count the number of saturdays and sundays between 2 dates:
DECLARE #from date = '2018-02-03'
DECLARE #to date = '2018-02-13'
SELECT
datediff(day, -2, #to)/7-datediff(day, -1, #from)/7 -- saturdays
+ datediff(day, -1, #to)/7-datediff(day, 0, #from)/7 -- sundays
Holidays are different from country to country.
This function will determine when Easter starts for a specific year:
CREATE FUNCTION [dbo].[f_EasterByYear] (#Year SMALLINT) RETURNS DATE AS
BEGIN
DECLARE
#c INT,
#n INT,
#k INT,
#i INT,
#j INT,
#l INT,
#m INT,
#d INT,
#Easter DATE
SET #c = (#Year / 100)
SET #n = #Year - 19 * (#Year / 19)
SET #k = (#c - 17) / 25
SET #i = #c - #c / 4 - ( #c - #k) / 3 + 19 * #n + 15
SET #i = #i - 30 * ( #i / 30 )
SET #i = #i - (#i / 28) * (1 - (#i / 28) *
(29 / (#i + 1)) * ((21 - #n) / 11))
SET #j = #Year + #Year / 4 + #i + 2 - #c + #c / 4
SET #j = #j - 7 * (#j / 7)
SET #l = #i - #j
SET #m = 3 + (#l + 40) / 44
SET #d = #l + 28 - 31 * ( #m / 4 )
SET #Easter = DATEADD(month, #m, DATEADD(year, #Year- 1900,
DATEADD(d, #d, '1899-11-30')))
RETURN #Easter
END
This function will return 1 for workday otherwise 0:
CREATE FUNCTION [dbo].[f_IsWorkDay] (#Date DATE) RETURNS BIT AS
BEGIN
DECLARE #IsWorkDay BIT = 1
SELECT #IsWorkDay =
CASE
--Exclude Saturday and Sunday (Datefirst-independent)
WHEN DATEDIFF(d, 0, #Date) % 7 IN (5,6) THEN 0
WHEN MONTH(#Date)=12 AND DAY(#Date) IN (24,25,26,31) THEN 0
--Exclude New Years
WHEN MONTH(#Date)=1 AND DAY(#Date)=1 THEN 0
--Exclude Easter, Pentecost and Ascension of Jesus
WHEN #Date IN (
DATEADD(DAY,-3,dbo.f_EasterByYear(YEAR(#Date))),
DATEADD(DAY,-2,dbo.f_EasterByYear(YEAR(#Date))),
DATEADD(DAY,1, dbo.f_EasterByYear(YEAR(#Date))),
DATEADD(DAY,26,dbo.f_EasterByYear(YEAR(#Date))),
DATEADD(DAY,39,dbo.f_EasterByYear(YEAR(#Date))),
DATEADD(DAY,50,dbo.f_EasterByYear(YEAR(#Date)))
) THEN 0
ELSE 1
END
RETURN #IsWorkDay
END
You can test if a day is a workday this way:
SELECT dbo.f_Isworkday(date) isWorkDay, date
FROM (values('2018-02-09'),('2018-02-10'),('2018-02-11'),('2018-02-12')) x(date)
Advanced test giving an interval as input:
DECLARE
#from DATE = '2018-02-09',
#to DATE = '2018-02-13'
;WITH N(N)AS
(SELECT 1 FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))M(N)),
tally(N)AS(SELECT ROW_NUMBER()OVER(ORDER BY N.N)FROM N,N a,N b,N c,N d,N e)
SELECT
dbo.f_Isworkday(dateadd(d, N-1, #from)) isworkday,
dateadd(d, N-1, #from) date
FROM tally
WHERE N <= datediff(d, #from, #to) + 1
Use this Function, It will work with your Calendar table too. All Active entries in the Calendar table will not be counted as working days
CREATE FUNCTION [dbo].[fnGetDaysWorked2] (#StartDate datetime, #EndDate datetime)
RETURNS int
BEGIN
DECLARE #tblNonWorking TABLE(dt DATETIME,dtw varchar(30))
While (#StartDate<=#EndDate)
BEGIN
INSERT INTO #tblNonWorking(dt,dtw) VALUES (#StartDate,Lower(DATENAME(dw, #StartDate)))
SET #StartDate=Dateadd(day,1,#StartDate)
END
DECLARE #WORKDAYS INT
SET #WORKDAYS=0
SELECT #WORKDAYS=COUNT(1)
FROM #tblNonWorking a
LEFT JOIN Calendar c ON a.dt=c.Holiday AND c.Active=1
WHERE a.dtw NOT IN ('saturday','sunday')
AND c.ID IS NULL
RETURN #WORKDAYS
END
Related
I would like to create a function in SQL Server that would return the no of working days after checking the day from the dimension table dimcalender which has all the working days from 2020 up until 2040
My Code:
Create FUNCTION [udf_WorkingDays]
(
-- Add the parameters for the function here
#StartDate DATETIME,
#EndDate DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE #TotalDays INT
SET #StartDate = '2022/06/01'
SET #EndDate = '2022/06/07'
SET #TotalDays = 0
--SET #TotalDays =(
WHILE (#StartDate <= #EndDate)
BEGIN
sum(select WorkingDay
FROM [XS].[dimCalendar]
where [Date] >= #StartDate
and [Date] <= #EndDate)
set #StartDate = DATEADD(day, 1, #StartDate)
--set #TotalDays = dss;)
END;
RETURN #TotalDays
END;
[dimCalendar] is where all UK working days are recorded.
WorkingDay is just a flag to say 0,1 (if its a working day or not.)
TotalDays is a variable that will have total no. of working days that will be returned.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #TotalDays INT
DECLARE #DDays INT
SET #StartDate = '2022/06/01'
SET #EndDate = '2022/06/06'
SET #TotalDays = 0
SET #TotalDays = (
SELECT a.toti
FROM (
SELECT SUM(WorkingDay) AS toti
FROM [dimCalendar]
WHERE [Date] >= #StartDate
AND [Date] <= #EndDate
) AS a
)
RETURN #TotalDays;
I am trying to generate a stock position report as at each month. within my query i have a date variable set at a specific month:
declare #date datetime = CONVERT(DATETIME, '2019-08-13 00:00:00', 121)
declare #lastmonth datetime = (select cast(dateadd(day, -day(getdate()), getdate()) as date))
declare #m int = 0
--**my aim is to ensure that #date = #lastmonth**
while #date<= #lastmonth
Begin
set #m = #m + 1
while #m < 12
Begin
set #date = (select dateadd(mm, #m, #date))
End
End
select
I need to display dates of all Mondays in the given date range.
For example, if my start date is 01/05/2015 and end date is 31/05/2015, I need to show
04/05/2015
11/05/2015
18/05/2015
25/05/2015
How is it possible?
This procedure is independent from regions and languages.
Please note the first line with SET DATEFIRST 1.
SET DATEFIRST 1; -- First day of the week is set to monday
DECLARE #DateFrom DateTime ='20150601', #DateTo DateTime = '20150630' ;
WITH CTE(dt)
AS
(
SELECT #DateFrom
UNION ALL
SELECT DATEADD(d, 1, dt) FROM CTE
WHERE dt < #DateTo
)
SELECT dt FROM CTE where datepart ("dw", dt) = 1;
Using a CTE it is possible this way..
DECLARE #DateFrom DateTime ='2015-05-01',
#DateTo DateTime = '2015-05-31'
;WITH CTE(dt)
AS
(
SELECT #DateFrom
UNION ALL
SELECT DATEADD(d, 1, dt) FROM CTE
WHERE dt < #DateTo
)
SELECT 'Monday', dt FROM CTE
WHERE DATENAME(dw, dt) In ('Monday')
Refer: Select dates of a day between two dates.
SELECT [Day],[Dt] FROM dbo.fnGetDatesforAday('7/1/2008','8/31/2008','Sunday')
CREATE FUNCTION fnGetDatesforAday
(
-- Add the parameters for the function here
#DtFrom DATETIME,
#DtTo DATETIME,
#DayName VARCHAR(12)
)
RETURNS #DateList TABLE ([Day] varchar(20),Dt datetime)
AS
BEGIN
IF NOT (#DayName = 'Monday' OR #DayName = 'Sunday' OR #DayName = 'Tuesday' OR #DayName = 'Wednesday' OR #DayName = 'Thursday' OR #DayName = 'Friday' OR #DayName = 'Saturday')
BEGIN
--Error Insert the error message and return
INSERT INTO #DateList
SELECT 'Invalid Day',NULL AS DAT
RETURN
END
DECLARE #TotDays INT
DECLARE #CNT INT
SET #TotDays = DATEDIFF(DD,#DTFROM,#DTTO)-- [NO OF DAYS between two dates]
SET #CNT = 0
WHILE #TotDays >= #CNT -- repeat for all days
BEGIN
-- Pick each single day and check for the day needed
IF DATENAME(DW, (#DTTO - #CNT)) = #DAYNAME
BEGIN
INSERT INTO #DateList
SELECT #DAYNAME,(#DTTO - #CNT) AS DAT
END
SET #CNT = #CNT + 1
END
RETURN
END
SET DATEFIRST 7; -- Set's sunday as first day of week, won't work otherwise
DECLARE #StartDate DATE = '06/01/2015'
DECLARE #EndDate DATETIME = '06/30/2015'
DECLARE #TableOfDates TABLE(DateValue DATETIME)
DECLARE #CurrentDate DATETIME
SET #CurrentDate = #startDate
WHILE #CurrentDate <= #endDate
BEGIN
INSERT INTO #TableOfDates(DateValue) VALUES (#CurrentDate)
SET #CurrentDate = DATEADD(DAY, 1, #CurrentDate)
END
SELECT * FROM #TableOfDates WHERE DATEPART(weekday,Datevalue) = 2
I am looking for a possibility to extract years, months and days from a date range.
Example:
From: 01/01/2012 To: 08/17/2014
Result: 2 years, 8 month and 17 days
I know how to code this but maybe someone has a genius solution how to do this in SQL with built in commands. Or already made a fast and good working function.
If not I will need to code a sql inline function and present this for documentation later here as answer.
So first of all I was thinking about the approach to change the orbit around the sun to correct it to exact 360 days a year. But this would probably ends in some bad side effects...
As I want the exact amount I write this (see below) and it seems to work as intended for my needs. I share this for discussions, improvements and to share if anyone need this, too.
DECLARE #start datetime;
DECLARE #end datetime;
DECLARE #current datetime;
DECLARE #year int
DECLARE #month int
DECLARE #days int;
DECLARE #daytmp int;
-- note start date earliest: 1/1/1753
SET #start = CAST('28.02.1753' AS datetime)
SET #end = GETDATE() --CAST('31.12.2016' AS datetime)
SET #current = #start
SET #days = (SELECT DATEDIFF(day, #start, #end))
SET #year = 0;
WHILE #days>365
BEGIN
SET #days = #days - (SELECT DATEDIFF(day, #current, DATEADD(YEAR, 1, #current)))
SET #current = DATEADD(YEAR, 1, #current)
SET #year = #year + 1
END
SET #month = 0;
SET #daytmp = #days
WHILE #daytmp>28
BEGIN
SET #daytmp = #days - (SELECT DATEDIFF(day, #current, DATEADD(MONTH, 1, #current)))
IF (#daytmp>0) BEGIN
SET #days = #daytmp
SET #current = DATEADD(MONTH, 1, #current)
SET #month = #month + 1
END
END
PRINT #year
PRINT #month
PRINT #days
I moved this in to table function that returns 3 values with position 1,2,3 so I can use it inside select statements.
CREATE FUNCTION dbo.sf_GetYearMonthDayFromRange(#start datetime, #end datetime)
RETURNS #result TABLE ([value] int, [position] int)
AS
BEGIN
DECLARE #current datetime;
DECLARE #year int
DECLARE #month int
DECLARE #days int;
DECLARE #daytmp int;
SET #current = #start
SET #days = (SELECT DATEDIFF(day, #start, #end))
SET #year = 0;
WHILE #days>365
BEGIN
SET #days = #days - (SELECT DATEDIFF(day, #current, DATEADD(YEAR, 1, #current)))
SET #current = DATEADD(YEAR, 1, #current)
SET #year = #year + 1
END
SET #month = 0;
SET #daytmp = #days
WHILE #daytmp>28
BEGIN
SET #daytmp = #days - (SELECT DATEDIFF(day, #current, DATEADD(MONTH, 1, #current)))
IF (#daytmp>0) BEGIN
SET #days = #daytmp
SET #current = DATEADD(MONTH, 1, #current)
SET #month = #month + 1
END
END
INSERT INTO #result SELECT #year, 1
INSERT INTO #result SELECT #month, 2
INSERT INTO #result SELECT #days, 3
RETURN
END
how about this one query
Declare #FDate DateTime = '01/01/2012',
#TDate DateTime = '08/17/2014'
Select Convert(Varchar,(TotDays/365)) + 'years, ' + Convert(Varchar,(TotDays%365)/30) + ' month and ' + Convert(Varchar,(TotDays%365%30)) + ' days'
From (
Select DATEDIFF(DAY,#FDate, #TDate) TotDays
) As DCal
and output above this query is :
Descr
---------------------------
2years, 7 month and 19 days
I am trying to get the date difference in a given date excluding the week days.
Here is what I have:
SELECT DATEADD (w, -4, GETDATE())
This returns 2013-05-04 19:01:53.170, which means that it also counts weekends.
Same for
SELECT DATEADD (dw, -4, GETDATE())
Any help will be appreciated.
Thanks in advance.
I'm using these functions that return the non-weekend seconds between two dates:
CREATE FUNCTION [dbo].[DateDiff_NoWeekends](
#date1 DATETIME,
#date2 DATETIME
)
RETURNS INT AS BEGIN
DECLARE #retValue INT
SET #date1 = dbo.__CorrectDate(#date1, 1)
SET #date2 = dbo.__CorrectDate(#date2, 0)
IF (#date1 >= #date2)
SET #retValue = 0
ELSE BEGIN
DECLARE #days INT, #weekday INT
SET #days = DATEDIFF(d, #date1, #date2)
SET #weekday = DATEPART(dw, #date1) - 1
SET #retValue = DATEDIFF(s, #date1, #date2) - 2 * 24 * 3600 * ((#days + #weekday) / 7)
END
RETURN #retValue
END
GO
CREATE FUNCTION [dbo].[__CorrectDate](
#date DATETIME,
#forward INT
)
RETURNS DATETIME AS BEGIN
IF (DATEPART(dw, #date) > 5) BEGIN
IF (#forward = 1) BEGIN
SET #date = #date + (8 - DATEPART(dw, #date))
SET #date = DateAdd(Hour, (8 - DatePart(Hour, #date)), #date)
END ELSE BEGIN
SET #date = #date - (DATEPART(dw, #date)- 5)
SET #date = DateAdd(Hour, (18 - DatePart(Hour, #date)), #date)
END
SET #date = DateAdd(Minute, -DatePart(Minute, #date), #date)
SET #date = DateAdd(Second, -DatePart(Second, #date), #date)
END
RETURN #date
END
Here's a sql-fiddle demo for all non-weekend days in april (22).
SELECT [no weekend days in april] =
(dbo.DateDiff_NoWeekends('2013-04-01','2013-05-01')
/ 3600 / 24)
The query below gives the difference for week days alone , Ie counts the no od days between two days and subtracts the no of weekend days ,
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SELECT #StartDate = '01-July-2008',
#EndDate = '30-July-2008'
;WITH DATE (Date1)
AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, '19000101', #StartDate), '19000101')
UNION ALL
SELECT DATEADD(DAY, 1, Date1)
FROM DATE
WHERE Date1 < #EndDate
)
SELECT count(*) -
(
SELECT count(*)
--CONVERT(VARCHAR(15),d1.DATE1 ,110) as [Working Date],
--DATENAME(weekday, d1.Date1) [Working Day]
from DATE d1 where (DATENAME(weekday, d1.Date1)) in ('Saturday','Sunday')
)
--CONVERT(VARCHAR(15),d1.DATE1 ,110) as [Working Date],
--DATENAME(weekday, d1.Date1) [Working Day]
from DATE d1 where (DATENAME(weekday, d1.Date1)) not in ('Saturday','Sunday')
please let me know for any clarifications
Maybe I am still missing some full testing, but this works for me too: take the difference in days and then subtract 2 days for each weekend
DateDiff(d, d1, d2) - 2*DateDiff(wk, d1, d2)
Could be put in a function as well