Related
For this month (Jan) the working days excluding bank holidays is 20.
Excluding bank holidays would take this to 19. I'm happy with this.
NOTE: I've manually added new years day in this example, I will be using a table which specifically has all bank holidays stored
This becomes troublesome when a month has multiple bank holidays (May). Because this feeds a daily run report, the bank holiday can't be excluded until the date has been reached/passed. (The report runs each day showing the previous day - e.g runs on the 2nd showing the 1st)
Using May as the example:
BH = 8th,25th
Report runs on the 7th showing 4 Working days
Report runs on the 8th showing 5 Working days
Report runs on the 11th showing 5 Working days
Report runs on the 22nd showing 14 Working days
Report runs on the 25th showing 15 Working days (Excludes this and previous BH)
Report runs on the 26th showing 15 Working days
CODE:
Declare #ReportedDays int
Declare #StartDate datetime
Declare #Enddate datetime
Declare #Days int
Declare #BankHoliday date
Declare #Working int
Set #ReportedDays = DAY(GETDATE())
Set #StartDate = DATEADD(DAY,(DATEPART(DAY,GETDATE())-1)*(-1),GETDATE())
Set #Enddate = GETDATE()
Set #BankHoliday = '2020-01-01'
set #Days =
datediff(dd, #StartDate, #EndDate) - (datediff(wk, #StartDate, #EndDate) * 2) -
case when datepart(dw, #StartDate) = 1 then 1 else 0 end +
case when datepart(dw, #EndDate) = 1 then 1 else 0 end
Set #Working = CASE when GETDATE() >= #BankHoliday then #Days - 1 Else #Days end
Select
#Working as 'Excluding Bank Holidays',
#Days as 'Excluding Weekends',
#ReportedDays 'Current(DAY)Date'
From Table1
When using the dateadd/datdiff trick to round dates I've always had very predictable and understandable results with days and months. I am now using it for the first time with weeks and am experiencing something I was not expecting.
The 19th and 26th of July 2015 seem to get moved into what I consider the next week. The following example illustrates the actual behavior in the second column and the expected behavior in the third column. I'm trying to understand WHY and HOW there is a difference.
declare #n as table ([N] [int] )
insert into #n ( [N] ) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)
select n
, dateadd(dd,n,'2015-jul-17')
, dateadd(wk,datediff(wk,0, dateadd(dd,n,'2015-jul-17') ),0)
, dateadd(dd,datediff(dd,0, dateadd(dd,n,'2015-jul-17') )/7*7,0)
from #n
order by n
The problem is DATEDIFF() by week using Sunday as the start day of the week instead of Monday.
A trick is -1:
declare #n as table ([N] [int])
declare #start datetime = '1900-01-01'
insert into #n ( [N] ) values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)
select n
, dateadd(dd,n,#start) as [Date]
,dateadd(wk,datediff(wk,0, dateadd(dd,n,#start) -1 ),0) as [WeekStart (by WeekDiff)]
,dateadd(dd,datediff(dd,0, dateadd(dd,n,#start) )/7*7,0) as [WeekStart (by DayDiff)]
, datediff(wk,0, dateadd(dd,n,#start)) [WeekDiff]
from #n
order by n
Is it possible to set start of week for T-SQL DATEDIFF function?
Edit
Using DATEPART(WEEKDAY) with SET DATEFIRST 1
SET DATEFIRST 1
;with dates(value) as (select convert(date, dateadd(dd,n,#start)) from #n)
select *, DATEADD(Day, -DATEPART(WEEKDAY, value) + 1, value) from dates
This date rounding algorithm, translated to English, could be worded as follows:
Add a number of [Time Unit] to the start of calendar (Day 0)
where [Time Unit] is Year/Month/Days/Hours etc..... And Start of calendar is 1900/01/01 00:00:00 in T-SQL.
1900/01/01 is a Monday.
So you are adding the number of full weeks to a Monday. And hence the week is always rounded to Monday. This throws 7/19 and 7/26 (Sundays) to the end of the week.
I have a booking table with the following information:
BookingID,(unique, not null)
StartDate, (not null)
EndDate (not null)
I need to calculate the number of nights someone remained in residence which I can do with a DATEDIFF between EndDate and StartDate. However, if someone is in residence for the entire month during a 31 day month we only charge them 30.
I'm not sure how to do this in SQL. I was thinking I would have to create a variable, calculate on a monthly basis and add to the variable, but that seems like it would be messy and take a very long time, especially towards the end of year. This needs to be done for about 5,000 records on a daily basis.
So:
If someone starts on 7/25/14 and ends 9/2/14, the nights is 38 not 39.
If someone starts on 10/2/14 and ends on 11/1/14, the nights is 30.
If someone starts on 10/2/14 and ends on 10/31/14, the nights is 29.
We will be calculating into the future so it doesn't matter if the end date is greater than the day the report is being ran.
Does anyone have any ideas how to accomplish this in the best way?
I would first to create a lookup table with all the month with 31 days
Such as
DECLARE #month TABLE (start_date date,end_date date)
INSERT INTO #month VALUES ('2014-07-01','2014-07-31'),('2014-08-01','2014-08-31'),('2014-10-01','2014-10-31'),('2014-12-01','2014-12-31')
//populate all months in your calculate range
Then you can calculate the value with
DECLARE #start DATE = '2014-07-25', #end DATE = '2014-09-02'
SELECT DATEDIFF(day,#start,#end) -
(SELECT COUNT(*) FROM #month WHERE start_date >= #start AND end_date <= #end)
Retrieve the integer part of the datediff divided by 31 :
SELECT DATEDIFF(day,'2014-07-25', '2014-09-02') - DATEDIFF(day,'2014-07-25', '2014-09-02') / 31
The SQLish solution is to create a calendar table that holds all the dates you will ever care about and any business meaning that those dates may have, such as "Is this a holiday?", "Is this off-season?", or "do we charge for this day?"
This may sound insane to someone accustomed to other programming languages, but it is perfectly sensible in the database world. Business rules and business logic get stored as data, not as code.
Make this table and populate it:
CREATE TABLE Calendar (
[date] date
,[is_charged] bit
)
and then you can write code that is nearly plain English:
SELECT
[BookingID]
,COUNT([date])
FROM BookingTable
INNER JOIN Calendar
ON [date] >= [StartDate]
AND [date] < [EndDate]
WHERE [is_charged] = 1
GROUP BY [BookingId]
When your business rules change, you just update the calendar instead of rewriting the code.
If I've read your question correctly then you can't actually use those solutions above which consist of a table of billable and non billable days because the 31st is billable unless the whole month was booked.
I reckon this is probably a job for a user defined function. Which runs up a total starting with the month that the start date is in and finishing with the month that the end date is in.
CREATE FUNCTION dbo.FN_BillableDays (#StartDate date, #EndDate date)
returns int
AS
BEGIN
IF #StartDate > #EndDate
BEGIN
return null --Error
END
DECLARE #Next date
DECLARE #MonthStart date
DECLARE #MonthEnd date
DECLARE #NextMonthStart date
DECLARE #n int =0
SET #Next = #StartDate
SET #MonthStart = DATEADD(day,1-DAY(#Next),#Next)
SET #NextMonthStart = DATEADD(month,1,#MonthStart )
SET #MonthEnd = DATEADD(day,-1,#NextMonthStart)
WHILE DATEDIFF(month,#Next,#EndDate) >0
BEGIN
SET #n = #n +
CASE
WHEN DAY(#next) = 1 AND DAY(#MonthEnd) = 31 THEN 30
WHEN DAY(#next) = 1 THEN DAY(#MonthEnd)
ELSE 1+DAY(#MonthEnd) -DAY(#next) END
SET #MonthStart = #NextMonthStart
SET #NextMonthStart = DATEADD(month,1,#MonthStart )
SET #MonthEnd = DATEADD(day,-1,#NextMonthStart)
SET #Next = #NextMonthStart
END
--Month of the EndDate
SET #n = #n +
CASE
WHEN DAY(#next) = 1 AND DAY(#EndDate) = 31 THEN 29
WHEN DAY(#next) = 1 THEN DAY(#EndDate)-1
ELSE DAY(#MonthEnd) -DAY(#EndDate) END
return #n
END
I tried it with some test dates
SELECT
b.BookingID,
b.StartDate,
b.EndDate,
dbo.FN_BillableDays (b.StartDate,b.EndDate) AS BillableDays
FROM dbo.Booking b
And got the following
BookingID StartDate EndDate BillableDays
----------- ---------- ---------- ------------
1 2013-12-31 2014-01-02 2
2 2013-12-31 2014-01-30 30
3 2014-01-01 2014-01-30 29
4 2014-01-01 2014-01-31 29
5 2014-01-01 2014-02-01 30
6 2014-01-01 2014-02-02 31
7 2014-02-02 2014-02-01 NULL
(7 row(s) affected)
Which matches my understanding of the logic you want to implement but you may want to tweak the last bit which adds on the days for the final month. If they leave on the 31st do you want to give them their last night (30th to 31st) for free.
If you don't then delete the line
WHEN DAY(#next) = 1 AND DAY(#EndDate) = 31 THEN 29
When a Report is run on a Monday it needs to set the default date to Friday ? Also 4 other conditions looking at the requirments. How to do this in an sql statement is the question.
So in pseudo code,
If today's date is Monday then set default date to Friday with full date format.
If today's date is Saturday then set default date to Friday with full date format.
If today's date is Sunday then set default date to Friday with full date format.
If any other day , then set default date to previous working day .
So need one sql statement maybe with a case statement .
Now I found these statements which give day of week so now need to do next part which may be a case statement or maybe a function ? This is the part I need assistance pl.
select datename(dw,getdate()) --Monday
select datepart(dw,getdate()) -- 1
SET DATEFIRST 1;
DECLARE #day DATE = SYSDATETIME();
SELECT #day = DATEADD
(
DAY,
CASE
WHEN DATEPART(WEEKDAY, #day) IN (1,2)
THEN -(DATEPART(WEEKDAY, #day)+1)
ELSE -1
END,
#day);
SELECT #day;
So, if the WEEKDAY (or dw) value is 3-6 (Tuesday through Saturday), then subtract one day. Otherwise subtract an additional day for Sunday and an addition two days for Monday.
Fortunately, the value for Sunday is 1 and the value for Monday is 2, so you can just subtract those values for those days.
DECLARE #DefaultDate datetime
SET #DefaultDate =
CASE
WHEN DATEPART(WEEKDAY, GETDATE()) IN (1,2) -- OR (1,7) outside the U.S.
THEN DATEADD(DAY, (-1 - DATEPART(WEEKDAY, GETDATE())), GETDATE())
ELSE
DATEADD(DAY, -1, GETDATE())
END
I am working on an attendance software in asp.net, in it i have to make a report which will tell the user about the hours and everything...so far i have created the basic functionality of the system, i.e. the user can check in and check out...i am stuck at making the report...
I have to calculate the working hours for every month, so the user can compare his hours with the total hours...what i had in mind was to create a stored procedure which when given a month name and a year, returns an int containing working hours for that month....but i can seem to get at it....
so far i found out how to create a date from a given month and a date, and found out the last day of that month, using which i can find out the total days in month...now i cant seem to figure out how do i know how much days to subtract for getting the working days.
here's the so far code..
declare
#y int,
#m int,
#d int,
#date datetime
set #y = 2012
set #m = 01
set #d = 01
----To create the date first
select #date = dateadd(mm,(#y-1900)* 12 + #m - 1,0) + (#d-1)
----Last Day of that date
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#date)+1,0))
any help will be appreciated guys, thanks in advance....
The #theDate is any date on the month you want to calculate the work days. This approach does not take care about holidays.
DECLARE #theDate DATETIME = GETDATE()
SELECT MONTH(#theDate) [Month], 20 + COUNT(*) WorkDays
FROM (
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 28) AS theDate
UNION
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 29)
UNION
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 30)
) AS d
WHERE DATEPART(DAY, theDate) > 28
AND DATEDIFF(DAY, 0, theDate) % 7 < 5
Here you can consider the below sql server code to get the first and
last day of the given month and also ignore all the Saturdays and Sundays.
DECLARE #curr_date datetime=getdate()
DECLARE #st_date datetime,#ed_date datetime
select #st_date=DATEADD(mm,datediff(mm,0,#curr_date),0),#ed_date = DATEADD(mm,datediff(mm,-1,#curr_date),-1)
--select #st_date as first_day,#ed_date as last_day
SET DATEFIRST 1 --Monday as first day of week
select DATEADD(dd,number,#st_date) from master..spt_values
where DATEDIFF(dd,DATEADD(dd,number,#st_date),#ed_date) >= 0 and type='P'
and DATEPART(DW,DATEADD(dd,number,#st_date)) <> 6
and DATEPART(DW,DATEADD(dd,number,#st_date)) <> 7
But inorder to calculate the actual working hours, you will have to take into the consideration of following thigs
1.Calculate the time interval between swipe-in and swipe-outs between start and end time for a day.
2.Exclude all the time gap(employee not in office)
3.Consider the company holidays.
etc
Here is a UDF to count work days. You can pass any date of a month to this function. But usually you should use actual "calendar" table to calculate work days and insert in this table weekends, holidays,... and so on.
CREATE FUNCTION dbo.WorkDaysCount (#Date datetime)
RETURNS int AS
BEGIN
DECLARE #BeginOfMonth datetime
SET #BeginOfMonth=DATEADD(DAY,-DAY(#Date)+1,#Date);
DECLARE #EndOfMonth datetime
SET #EndOfMonth=DATEADD(Day,-1,DATEADD(Month,1,#BeginOfMonth));
DECLARE #cDate datetime
set #cDate=#BeginOfMonth
Declare #WorkDaysCount int
SET #WorkDaysCount=0
while #cDate<=#EndOfMonth
begin
if DATEPART(dw,#cDate) not in (1,7) SET #WorkDaysCount=#WorkDaysCount+1 -- not a Sunday or Saturday change (1,7) to (6,7) if you have other week start day (Monday).
set #cDate=#cDate+1;
end;
return (#WorkDaysCount);
END