sql ignore days in the week with datefirst - sql-server

I am working in a algorithm that needs to be calculated based on the number of working days.
Sample data
CREATE TABLE Holidays
(
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO Holidays VALUES ('2019-07-16', '2019-07-17')
DECLARE #CurrentWeekDay INT
SET DATEFIRST 1
// Returns day of the week as int, ex... Monday = 0, Tue = 1....
SET #CurrentWeekDay = DATEPART (WEEKDAY, GETDATE()) - 1
this starts on Monday until Friday, 0,1,2,3,4, which is subtracted on the total days of the week
Now i have created 2 new variables
DECLARE #TotalHolidays INT, #IsSameWeek BIT
// If there is a holiday in the current week get the holiday date, else just cast 0
SET #IsSameWeek =
CASE
WHEN (SELECT DATEDIFF(WEEK,GETDATE(), (SELECT TOP 1 EndDate FROM Holidays))) = -1 THEN 0
ELSE
1
END
SET #TotalHolidays =
CASE
WHEN #IsSameWeek = 1 THEN (SELECT DATEDIFF(day, StartDate, EndDate ) FROM Holidays)
ELSE
0
END
so basically the total amount of days left in the week is calculated using this simple formula
DECLARE #TotalDays = 5,
SET #TotalDays = #TotalDays - #TotalHolidays - #CurrentWeekDay
this is all nice but this is only effective when the holidays start at the end of the week.
Datefirst
M T W T F
0 1 2 3 4
if Thursday and friday are holidays the algorithm works nicely, but if Wednesday is a holiday then it ruins everything
basically i need it so if wednesday is a holiday
Datefirst goes like
M T W T F
0 1 2 3
With all set this is the whole code
CREATE TABLE Holidays
(
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO Holidays VALUES ('2019-07-18', '2019-07-19')
DECLARE #TotalHolidays INT, #IsSameWeek BIT, #CurrentWeekDay INT
SET DATEFIRST 1
SET #CurrentWeekDay = DATEPART (WEEKDAY, GETDATE()) - 1
SET #IsSameWeek =
CASE
WHEN (SELECT DATEDIFF(WEEK,GETDATE(), (SELECT TOP 1 EndDate FROM Holidays))) = -1 THEN 0
ELSE
1
END
SET #TotalHolidays =
CASE
WHEN #IsSameWeek = 1 THEN (SELECT DATEDIFF(day, StartDate, EndDate ) FROM Holidays)
ELSE
0
END
DECLARE #DaysLeft INT
SET #DaysLeft = 5 - #TotalHolidays - #CurrentWeekDay
This will return 0 when it should return 2

Related

Display last business day of every month in the database table. (Business day does not include Saturday, Sunday)

I want to display last business day (excludes Saturday, Sunday) of every month in the database table.
We use pgadmin, I'm guessing it's similar to SQL Server, here is a query we use:
bolReturn = true;
--Get the day of week
SELECT EXTRACT(DOW FROM _start)
Into dayofweek;
--Sataurday
if dayofweek = 6 then
bolReturn = false;
--Sunday
ELSIF dayofweek = 0 then
bolReturn = false;
end if;
--Check against office closing
select count(*) as start
into intCount
from tables.officeclosed where closeddate = _start;
if intCount > 0 then
bolReturn = false;
end if;
return
tables.officeclosed would contain days you know you have off such as holidays and _start is the date you are passing in.
Try this:
create function dbo.LastBusinessDayOfMonth ( #Date date )
returns date as
begin
declare #Result date;
-- Find last day of the month
set #Result = EOMONTH(#Date);
-- If this date is Saturday or Sunday,
-- choose the preceding date
while DATEDIFF(day,'0001-01-01',#Result)%7 >= 5
set #Result = DATEADD(day,-1,#Result)
return #Result;
end
Certainly need more info, and as mentioned by Sean, you need to consider public holidays etc.
Following uses cross apply with "TOP 1" from subquery selecting 3 dates.
SET DATEFIRST 1
select *
from
(
SELECT Months.Idx, EOMONTH(datefromparts(year(getdate()), Months.idx, 1)) as EOMDate
FROM ( VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12) ) Months(Idx)
) Months
cross apply (
select top 1 mldx.date AS LastBusinessDay
from
(
select Months.EOMDate date
union all select dateadd(day, -1, Months.EOMDate )
union all select dateadd(day, -2, Months.EOMDate )
) mldx
where datepart(weekday, mldx.date ) <= 5
order by mldx.date desc
) lastBusinessDay

SQL Server : Recursive function to get last working day

I have a list holidays stored in BankHolidays table for a couple of years. Now i need to compute the last working day for any given date.
CTE may help in this scenario, however i prefer to have this snippet as a function.
I have written the following (pseudo) code to get my result, but i'm not able to do a recursive call
CREATE FUNCTION dbo.Get_Previous_Working_Day(#Day date) RETURNS date
AS BEGIN
if(datename(dw,#Day) = 'Sunday')
set #Day = DATEADD(day, -1, #Day)
if(datename(dw,#Day) = 'Saturday')
set #Day = DATEADD(day, -1, #Day)
if not exists (select count(1) from BankHolidays where datepart(yyyy,HolidayDate) = datepart(yyyy,#Day))
return null
else
begin
if exists (select count(1) from BankHolidays where convert(date,HolidayDate) = convert(date,#Day))
begin
set #Day = DATEADD(day, -1, #Day)
dbo.Get_Previous_Working_Day(#Day) --This recurise call may need to be modified
end
else
return #Day
end
end
Thank you in advance
Edit 1
Error : Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32).
I guess this is due to the stack over flow on recursive calls and its not able to decide when to quit. Seems like a logical error. Unfortunately i couldn't figure where its going wrong
BankHolidays Table:
--------------------------------------------------
HolidayDate DayofWeek Description
--------------------------------------------------
2015-01-01 Thursday New year
2010-01-01 Friday New year
2015-04-03 Friday Good Friday
2015-05-04 Monday Early May bank holiday
2014-06-11 Wednesday June 14 - NEW ENTRY
2015-05-25 Monday Spring bank holiday
2015-12-28 Monday Boxing Day (substitute day)
2015-04-06 Monday Easter Monday
2015-08-31 Monday Summer bank holiday
2015-12-25 Friday Christmas Day
Expected Output
Get_Previous_Working_Day('2015-01-01') -- Result : 2014-12-31
Get_Previous_Working_Day('2015-01-02') -- Result : 2014-12-31
Get_Previous_Working_Day('2010-01-04') -- Result : 2009-12-31
Get_Previous_Working_Day('2015-04-06') -- Result : 2015-04-02
Get_Previous_Working_Day('2015-12-05') -- Result : 2015-12-04
Get_Previous_Working_Day('2015-12-06') -- Result : 2015-04-04
Get_Previous_Working_Day('2014-06-12') -- Result : 2014-06-10
CREATE TABLE dbo.BankHolidays
(
HolidayDate DATE PRIMARY KEY,
[DayofWeek] AS DATENAME(dw, HolidayDate),
[Description] VARCHAR(100)
)
INSERT INTO dbo.BankHolidays (HolidayDate, [Description])
VALUES
('2015-01-01', 'New year'),
('2010-01-01', 'New year'),
('2015-04-03', 'Good Friday'),
('2015-05-04', 'Early May bank holiday'),
('2014-06-11', 'June 14 - NEW ENTRY'),
('2015-05-25', 'Spring bank holiday'),
('2015-12-28', 'Boxing Day (substitute day)'),
('2015-04-06', 'Easter Monday'),
('2015-08-31', 'Summer bank holiday'),
('2015-12-25', 'Christmas Day')
GO
CREATE FUNCTION dbo.Get_Previous_Working_Day
(
#date DATE
)
RETURNS DATE
AS BEGIN
DECLARE #result DATE
;WITH cte AS
(
SELECT dt = DATEADD(DAY, -1, #date)
UNION ALL
SELECT DATEADD(DAY, -1, dt)
FROM cte
WHERE dt > DATEADD(DAY, -30, #date)
)
SELECT TOP(1) #result = dt
FROM cte
WHERE dt NOT IN (SELECT t.HolidayDate FROM dbo.BankHolidays t)
AND DATENAME(dw, dt) NOT IN ('Saturday', 'Sunday')
ORDER BY dt DESC
OPTION (MAXRECURSION 0)
RETURN #result
END
GO
SELECT dbo.Get_Previous_Working_Day('2015-12-29')
#Ramu,
Please find the corrected part here.
There are two issues in the function definition. One is in the calling of function. Since the function should be called by using SELECT statement so you have to use that.
Second, the function should end by return statement.
So the correct definition would be:
CREATE FUNCTION dbo.Get_Previous_Working_Day(#Day date) RETURNS date
AS BEGIN
if(datename(dw,#Day) = 'Sunday')
set #Day = DATEADD(day, -1, #Day)
if(datename(dw,#Day) = 'Saturday')
set #Day = DATEADD(day, -1, #Day)
if not exists (select count(1) from BankHolidays where datepart(yyyy,HolidayDate) = datepart(yyyy,#Day))
return null
else
begin
if exists (select count(1) from BankHolidays where convert(date,HolidayDate) = convert(date,#Day))
begin
set #Day = DATEADD(day, -1, #Day)
select #Day = dbo.Get_Previous_Working_Day(#Day) --This recurise call may need to be modified
return #Day
end
else
return #Day
end
return #Day
end

Creating a date from Week of month and Day of week in SQL server

I have to get/create date from the user input of week of month (week number in that month - 1st,2nd,3rd,4th and last) and day of week (sunday,monday..) in SQL server.
Examples:
4th Sunday of every month, Last Friday of every month, First Monday etc.
I was able to do it easily in .net but SQL server does seem limited in the date functions.
I am having to use lot of logic to get the date. To calculate the date using the above two parameters I had to use lot of datepart function.
Any suggestions on how to come up with the optimal SQL query for such a function?
I created a function other day for another OP GET Month, Quarter based on Work Week number
This function takes the current year as default it can be further modified to take Year as a parameter too.
an extension to that function can produce the results you are looking for ....
WITH X AS
(
SELECT TOP (CASE WHEN YEAR(GETDATE()) % 4 = 0 THEN 366 ELSE 365 END)-- to handle leap year
DATEADD(DAY
,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1
, CAST(YEAR(GETDATE()) AS VARCHAR(4)) + '0101' )
DayNumber
From master..spt_values
),DatesData AS(
SELECT DayNumber [Date]
,DATEPART(WEEKDAY,DayNumber) DayOfTheWeek
,DATEDIFF(WEEK,
DATEADD(WEEK,
DATEDIFF(WEEK, 0, DATEADD(MONTH,
DATEDIFF(MONTH, 0, DayNumber), 0)), 0)
, DayNumber- 1) + 1 WeekOfTheMonth
FROM X )
SELECT * FROM DatesData
WHERE DayOfTheWeek = 6 -- A function would expect these two parameters
AND WeekOfTheMonth = 4 -- #DayOfTheWeek and #WeekOfTheMonth
Here is a general formula:
declare #month as datetime --set to the first day of the month you wish to use
declare #week as int --1st, 2nd, 3rd...
declare #day as int --Day of the week (1=sunday, 2=monday...)
--Second monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select dateadd(
day,
((7+#day) - datepart(weekday, #month)) % 7 + 7 * (#week-1),
#month
)
You can also find the last, 2nd to last... etc with this reverse formula:
--Second to last monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select
dateadd(
day,
-((7+datepart(weekday, dateadd(month,1,#month)-1)-#day)) % 7 - 7 * (#week-1),
dateadd(month,1,#month)-1
)

Stored Procedure to check if four weeks with less then two records

I am working on a stored procedure. The input is going to be a date and ID and the procedure is going to set a value to true if there are 4 weeks with less then 2 inputs per week.
It has to take in consideration that I might pass an early date with no records in the database.
I couldn't format the code. I don't know why?
So far I got that with previous help from you guys:
CREATE proc [dbo].[sp_test] (#id int, #d date)
as
declare #WeekFirstRecord as int
declare #WeeksWithNoRecords as int
SET #WeekFirstRecord = datepart(week,(select Min(ZeroPointIncidentDate) from EmployeeZeroPointIncidents where ZeroPointIncidentDate > #d))
SET #WeeksWithNoRecords = datepart(week, #WeekFirstRecord) - datepart(week, #d)
select case when sum(c) + #WeeksWithNoRecords >= 4 then 'true' else 'false' end status
from (
select c = count(*) over (partition by EmpId, datepart(week, ZeroPointIncidentDate))
from EmployeeZeroPointIncidents
where EmpId = #id and ZeroPointIncidentDate >= #d
) a
where c = 1
In my data only the weeks with the stars have less than two inputs and if I pass the date 7-7-2015 is going to set the output value to true
Any help will be appreciated. Do I need to iterate through every record and set a counter if less then two inputs or there is an easier way ?
ID Date
1 7-7-2015
2 6-23-2015
3 6-12-2015
1 7-8-2015
1 7-14-2015 *
1 7-21-2015 *
1 7-27-2015
1 7-28-2015
1 7-29-2015
1 7-30-2015
1 8-3-2015 *
1 8-11-2015 *
If I had week Jul 13 - no records week Jul 20 - no records week Jul 27 - 2 records Week Aug 3 - no records Week Aug 10 - 2 records Week Aug 17 - no records And pass Jul 12 date should return true, if I pass jul 15 should return false
I had to see your sample data set from your last question along with explanation from your last question as well as the explanation given in this question to come up with this solution.
When you ask a question here put yourself in the reader's shoes and see if the question makes any sense, anyway I hope this solution will get you what you want. cheers
CREATE PROCEDURE get_output
#Date DATE
,#ID INT
,#Output INT OUTPUT -- 1 true , 0 false
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Count INT;
SELECT #Count = COUNT(*)
FROM TableName
WHERE CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, #Date), 0) AS DATE)
= CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, [Date]), 0) AS DATE)
AND ID = #ID
GROUP BY CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, [Date]), 0) AS DATE)
IF (#Count < 2)
SET #Output = 1;
ELSE
SET #Output = 0;
END

How do I take a day of the year and 'bucket it' into weeks of the year in Microsoft SQL? Used in manufacturing scenarios for material requirements

I have a need to create a gross requirements report that takes how much supply and demand of a item in inventory from a start date onwards and 'buckets' it into different weeks of the year so that material planners know when they will need a item and if they have enough stock in inventory at that time.
As an example, today’s date (report date) is 8/27/08. The first step is to find the date for the Monday of the week the report date falls in. In this case, Monday would be 8/25/08. This becomes the first day of the first bucket. All transactions that fall before that are assigned to week #0 and will be summarized as the beginning balance for the report. The remaining buckets are calculated from that point. For the eighth bucket, there is no ending date so any transactions after that 8th bucket start date are considered week #8.
WEEK# START DATE END DATE
0.......None..........8/24/08
1.......8/25/08.......8/31/08
2.......9/1/08.........9/7/08
3.......9/8/08.........9/14/08
4.......9/15/08.......9/21/08
5.......9/22/08.......9/28/08
6.......9/29/08.......10/5/08
7.......10/06/08.....10/12/08
8.......10/13/08......None
How do I get the week #, start date, end date for a given date?
I've always found it easiest and most efficient (for SQL Server) to construct a table with one row for every week into the future through your domain horizon; and join to that (with a "WHERE GETDATE() >= MONDATE AND NOT EXISTS (SELECT 1 FROM table WHERE MONDATE < GETDATE())".
Anything you try to do with UDF's will be much less efficient and I find more difficult to use.
You can get Monday for any given date in a week as:
DATEADD(d, 1 - DATEPART(dw, #date), #date)
and you can write a stored procedure with the following body
-- find Monday at that week
DECLARE #currentDate SMALLDATETIME
SELECT #currentDate = DATEADD(d, 1 - DATEPART(dw, #date), #date)
-- create a table and insert the first record
DECLARE #weekTable TABLE (Id INT, StartDate SMALLDATETIME, EndDate SMALLDATETIME)
INSERT INTO #weekTable VALUES (0, NULL, #currentDate)
-- increment the date
SELECT #currentDate = DATEADD(d, 1, #currentDate)
-- iterate for 7 more weeks
DECLARE #id INT
SET #id = 1
WHILE #id < 8
BEGIN
INSERT INTO #weekTable VALUES (#id, #currentDate, DATEADD(d, 6, #currentDate))
SELECT #currentDate = DATEADD(ww, 1, #currentDate)
SET #id = #id + 1
END
-- add the last record
INSERT INTO #weekTable VALUES (8, #currentDate, NULL)
-- select the values
SELECT Id 'Week #', StartDate 'Start Date', EndDate 'End Date'
FROM #weekTable
When I pass
#date = '20080827'
to this procedure, I get the following
Week # Start Date End Date
0 NULL 2008-08-24 00:00:00
1 2008-08-25 00:00:00 2008-08-31 00:00:00
2 2008-09-01 00:00:00 2008-09-07 00:00:00
3 2008-09-08 00:00:00 2008-09-14 00:00:00
4 2008-09-15 00:00:00 2008-09-21 00:00:00
5 2008-09-22 00:00:00 2008-09-28 00:00:00
6 2008-09-29 00:00:00 2008-10-05 00:00:00
7 2008-10-06 00:00:00 2008-10-12 00:00:00
8 2008-10-13 00:00:00 NULL
--SQL sets the first day of the week as sunday and for our purposes we want it to be Monday.
--This command does that.
SET DATEFIRST 1
DECLARE
#ReportDate DATETIME,
#Weekday INTEGER,
#NumDaysToMonday INTEGER,
#MondayStartPoint DATETIME,
#MondayStartPointWeek INTEGER,
#DateToProcess DATETIME,
#DateToProcessWeek INTEGER,
#Bucket VARCHAR(50),
#DaysDifference INTEGER,
#BucketNumber INTEGER,
#NumDaysToMondayOfDateToProcess INTEGER,
#WeekdayOfDateToProcess INTEGER,
#MondayOfDateToProcess DATETIME,
#SundayOfDateToProcess DATETIME
SET #ReportDate = '2009-01-01'
print #ReportDate
SET #DateToProcess = '2009-01-26'
--print #DateToProcess
SET #Weekday = (select DATEPART ( dw , #ReportDate ))
--print #Weekday
--print DATENAME(dw, #ReportDate)
SET #NumDaysToMonday =
(SELECT
CASE
WHEN #Weekday = 1 THEN 0
WHEN #Weekday = 2 THEN 1
WHEN #Weekday = 3 THEN 2
WHEN #Weekday = 4 THEN 3
WHEN #Weekday = 5 THEN 4
WHEN #Weekday = 6 THEN 5
WHEN #Weekday = 7 THEN 6
END)
--print #NumDaysToMonday
SET #MondayStartPoint = (SELECT DATEADD (d , -1*#NumDaysToMonday, #ReportDate))
--print #MondayStartPoint
SET #DaysDifference = DATEDIFF ( dd , #MondayStartPoint , #DateToProcess )
--PRINT #DaysDifference
SET #BucketNumber = #DaysDifference/7
--print #BucketNumber
----Calculate the start and end dates of this bucket------
PRINT 'Start Of New Calc'
print #DateToProcess
SET #WeekdayOfDateToProcess = (select DATEPART ( dw , #DateToProcess ))
print #WeekdayOfDateToProcess
SET #NumDaysToMondayOfDateToProcess=
(SELECT
CASE
WHEN #WeekdayOfDateToProcess = 1 THEN 0
WHEN #WeekdayOfDateToProcess = 2 THEN 1
WHEN #WeekdayOfDateToProcess = 3 THEN 2
WHEN #WeekdayOfDateToProcess = 4 THEN 3
WHEN #WeekdayOfDateToProcess = 5 THEN 4
WHEN #WeekdayOfDateToProcess = 6 THEN 5
WHEN #WeekdayOfDateToProcess = 7 THEN 6
END)
print #NumDaysToMondayOfDateToProcess
SET #MondayOfDateToProcess = (SELECT DATEADD (d , -1*#NumDaysToMondayOfDateToProcess, #DateToProcess))
print #MondayOfDateToProcess ---This is the start week
SET #SundayOfDateToProcess = (SELECT DATEADD (d , 6, #MondayOfDateToProcess))
PRINT #SundayOfDateToProcess
The problem I see with the one bucket at a time approach is that its hard to make it scale,
If you join into a user defined function you will get better performance, you could use this a a starting point
Why not use a combination of DATEPART(year, date-column) and DATEPART(week, date-column) and group by these values. This works if the week in DATEPART is aligned on Mondays as ISO 8601 requires. In outline:
SELECT DATEPART(year, date_column) AS yyyy,
DATEPART(week, date_column) AS ww,
...other material as required...
FROM SomeTableOrOther
WHERE ...appropriate filters...
GROUP BY yyyy, ww -- ...and other columns as necessary...

Resources