Counting working days between two dates Oracle - sql

I'm trying to write a query which can count the number of working days between a payment being received and being processed, I started playing around with this for payments received in December 2017;
select unique trunc(date_received),
(case when trunc(date_received) in ('25-DEC-17','26-DEC-17') Then 0 when
to_char(date_received,'D') <6 Then 1 else 0 end) Working_day
from payments
where date_received between '01-DEC-17' and '31-dec-17'
order by trunc(date_received)
But to be honest, I'm at a loss as to how to take it further and add in date_processed and count the number of working days between date_processed and date_received... Any help would be much appreciated...

Maybe not the most optimal, but it works quite nicely, and it's easy to incorporate more complicated checks, like holidays. This query first generates all dates between the two dates, and then lets you filter out all the days that 'don't count'.
In this implementation I filtered out only weekend days, but it's quite easy to add checks for holidays and such.
with
-- YourQuery: I used a stub, but you can use your actual query here, which
-- returns a from date and to date. If you have multiple rows, you can also
-- output some id here, which can be used for grouping in the last step.
YourQuery as
(
select
trunc(sysdate - 7) as FromDate,
trunc(sysdate) as ToDate
from dual),
-- DaysBetween. This returns all the dates from the start date up to and
-- including the end date.
DaysBetween as
(
select
FromDate,
FromDate + level - 1 as DayBetween,
ToDate
from
YourQuery
connect by
FromDate + level - 1 <= ToDate)
-- As a last step, you can filter out all the days you want.
-- This default query only filters out Saturdays and Sundays, but you
-- could add a 'not exists' check that checks against a table with known
-- holidays.
select
count(*)
from
DaysBetween d
where
trim(to_char(DAYINBETWEEN, 'DAY', 'NLS_DATE_LANGUAGE=AMERICAN'))
not in ('SATURDAY', 'SUNDAY');

Related

How to make SSRS subscriptions Kick off on Business Day 3 of month

I am trying to figure out how to use the Data-Driven Subscription portion of SSRS to fire out a report to a bunch of people via email on the third business day of the month.
I'm a bit new to SQL but am learning very quickly, this just happens to be out of my small realm of knowledge.
I do have a table full of days of months, what year, what month, days of the week and all of that in different date formats. I just can't figure it out, I feel it's within my grasp of understanding though.
So far this is what I have and I feel like this could be summarized into a different easier sql statement? More optimized I guess.
select distinct --(CASE --when day_of_week = (2,3,4,5,6) then dateadd(day,1,day_desc_01) --when day_of_week = (7) then dateadd(day,2,day_desc_01) else day_of_week end) as 'BD_Date' day_of_week , day_desc_01 , date from Company.dbo.Company_Calendar where year = 2023 and day_of_week not in (1,7) and date <> '1900-01-01' and day_weekday_ct = 1 and year = 2023
I just want it to return the 3rd business day of the month for every month. Then probably a statement that says if it is the 3rd business day, fire off the report, if not, do nothing. I hope this makes a little bit of sense? I could also be way off track on this and way in over my head.
Thank you for your time and help!
This is a generic exmaple, but you should be able to apply the logic to your company calendar table.
This query will simply give you all the 3rd business days in the month. It filters out weekends and public holidays (assuming you have some way of recording the public holidays in your table). Once the filters are applied, if just assigns a row number to each records within in each calendar year/month and the only returns those where it is 3.
There are some extra columns included for clarity.
You may be able to simplify this but I dont; know what your calendar table contains so this assumes it has just the date (TheDate) and a column to indicate PublicHoliday.
CREATE VIEW ThirdBusDay AS
SELECT
*
FROM (
SELECT
TheDate
, DayOfWeek = DATEPART(WeekDay, TheDate)
, TheYear = YEAR(TheDate)
, TheMonth = MONTH(TheDate)
, TheDay = DAY(TheDate)
, WorkingDayOfMonth = ROW_NUMBER() OVER(PARTITION BY YEAR(TheDate), MONTH(TheDate) ORDER BY TheDate)
FROM myCalendarTable
WHERE
DATEPART(WeekDay, TheDate) NOT IN (7,1) -- Filter out Saturday and Sunday, change this if system week start is not Sunday.
AND
PublicHoliday = 0 -- filter out public holidays etc..
) d
WHERE WorkingDayOfMonth = 3
So if CAST(GetDate() AS Date) exists in this view then you know you have to execute the report.

Select Month w/ Most Days in Multi Month Range in SQL

Sorry if this has been asked, but I didn't find anything when searching: I have a large table of ~100k rows in SQL Server. Within each row is a date range, which in many cases spreads across multiple months (and years to a lesser extent). The ranges are typically about 30-35 days however they usually don't start at the the 1st of the month. An example of a typical date range is 01/10/2017-02/11/2017.
I'm looking for the most efficient way to output the month with the most days within in that range as it's own column. I'm doing the same thing for the year
Right now I have the following in my query:
SELECT DISTINCT
a.START_DATE,
a.END_DATE,
cast(month(dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date)) as tinyint) as Main_Month,
cast(year(dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date)) as smallint) as Main_Year
FROM TABLE
The output from that query using the above date range example would give me:
Start_Date: 01/10/2017 End_Date: 02/11/2017 Main_Month: 1 Main Year: 2017
That method has worked alright, but it slows when being done for all rows in the table. Are there any more efficient alternatives that I can use for the Main_Month and Main_Year columns?
EDIT IN RESPONSE TO COMMENTS:
By "month w/ the most days within a date range", using my example range of 01/10/2017-02/11/2017, since that range contains 21 days in January and only 11 in February, the output I'd get for Main_Month is 1. Also since the year is 2017 throughout the range the output I'd get for Main_Year is 2017
For ties, I'd go with the 1st month containing the max # of days. In the example of, 6/20/2017 - 9/5/2017 I'd go with 7, since that is the 1st month with 31 days in the range
As discused in the comments above, this solution is not perfectly accurate, but it mirrors the accuracy of the original slower solution:
SELECT b.START_DATE, b.END_DATE, month(b.mid_point) as Main_Month, year(b.midpoint) as Main_Year FROM
(SELECT DISTINCT
a.START_DATE,
a.END_DATE,
dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date) as mid_Point
FROM a) as b
You should be able to speed it up by making these two changes. First, only compute the datediff and dateadd once, then take it from the derived table to get the two fields you need. Next, don't bother with casting, since Month() and year() both do that for you. Were you able to see a speed difference with this method?
I think using CASE statements could be more efficient, and if you can avoid casting, like this:
, CASE WHEN datediff(day, a.Start_Date, a.End_Date) + 1 - DAY(a.END_Date) < DAY(a.END_DATE) THEN MONTH(a.End_Date)
ELSE MONTH(a.Start_Date) END AS Main_Month
, CASE WHEN YEAR(a.END_Date) = YEAR(a.Start_Date) THEN YEAR(a.Start_Date)
WHEN datediff(day, a.Start_Date, a.End_Date) + 1 - DAY(a.END_Date) < DAY(a.END_DATE) THEN YEAR(a.End_Date)
ELSE YEAR(a.Start_Date) END AS Main_Year

SSRS. Workday Function

I'm trying to convert the below Excel formula into SSRS but having looked around I cannot seem to find a solution. I can calculate the number of working days between two dates but what I'm trying to do is add on a number of working days to a date. Essentially I don't have the 2nd date.
I guess it would be something along the lines of the DATEADD function?
=WORKDAY($A1,B$1)
Hope someone can help
Many thanks
Here is a tsql solution to add X Business Days to a date.
declare #calendar as table (theDate date, dayOfWeek varchar (10));
declare #startDate as date = '20170704';
declare #businessDaysToAdd as integer = 10;
insert into #calendar
select theDate
, datename(dw, theDate) dow
from
dbo.dateTable('20170701', '20170720') ;
with temp as (
select theDate
, dayOfWeek
, rank() over (order by theDate) theRank
from #calendar
where theDate > #startDate
and dayOfWeek not in ('Saturday', 'Sunday')
)
select * from temp
where theRank = #businessDaysToAdd;
Notes
dbo.DateTable is a table valued function that just happens to exist in the database I was using. In real life, you might have an actual calendar table of some sort.
This example does not include holidays.
This is only the start of the answer to the posted question. It only solves the problem of Essentially I don't have the 2nd date.
Type this into the expression for the textbox. (From SSRS 2008 Datediff for Working Days)
=(DateDiff(DateInterval.day,Parameters!STARTDATE.Value,Parameters!ENDDATE.Value)+1)
-(DateDiff(DateInterval.WeekOfYear,Parameters!STARTDATE.Value,Parameters!ENDDATE.Value)*2)
-(iif(Weekday(Parameters!STARTDATE.Value) = 7,1,0)
-(iif(Weekday(Parameters!ENDDATE.Value) = 6,1,0))-1)
Ok after much perseverance I managed to get what I wanted in both TSQL and SSRS. My objective was to measure Agent productivity so I didn’t want to count the weekend and this would be unfair. If a date fell on a weekend then I wanted it to jump to a Monday. Likewise if adding number of days onto a date went over a weekend in the future then I needed the incremented date to reflect this. For the end user (In SSRS) I wanted a leading edge (Like an Upside down triangle) so that if the date + number working days was in the future then set to NULL, showing a zero would look like no productivity which is incorrect.
First TSQL - My base query started with the following SO thread but after trying many of the options I was finding when the date fell on a Saturday or Sunday the solution did not work for me (I was unable to create functions due to permissions). However tweaking the below got me there and I dealt with Sunday specifically
Add business days to date in SQL without loops
SELECT
,DATEADD(WEEKDAY, (/*Your Working Days*//5)*7+(/*Your Working Days*/ % 5) +
(CASE WHEN DATEPART(WEEKDAY,/*Your Date*/) <>7 AND DATEPART(WEEKDAY,/*Your Date*/) + (/*Your Working Days*/ % 5) >5 THEN 2
WHEN DATEPART(WEEKDAY,/*Your Date*/) = 7 AND DATEPART(WEEKDAY,/*Your Date*/) + (/*Your Working Days*/ % 5) >5 THEN 1 ELSE 0 END), /*Your Date*/) AS [IncrementedDate]
FROM /*YourTable*/
Then for SSRS - The 2 key points here is that TSQL will divide as an integer if the source number is an integer so this needs to be handled in SSRS and secondly you need to set the first week day to Monday as part of the expression.
I put this expression into a Matrix with Date Created being my Row Group and Contact Working Days being my Column Group.
=DATEADD("W",(INT(ReportItems!ContactWorkingDays.Value/5))*7+(ReportItems!ContactWorkingDays.Value MOD 5) + IIF(DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) <> 7 AND (DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) + (ReportItems!ContactWorkingDays.Value MOD 5) >5),2,IIF(DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) = 7 AND (DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) + (ReportItems!ContactWorkingDays.Value MOD 5) >5),1,0)),ReportItems!DateCreated.Value)
This does not include holidays - I'm not too bothered at this stage and that is for a rainy day! :)

Get date for nth day of week in nth week of month

I have a column with values like '3rd-Wednesday', '2nd-Tuesday', 'Every-Thursday'.
I'd like to create a column that reads those strings, and determines if that date has already come this month, and if it has, then return that date of next month. If it has not passed yet for this month, then it would return the date for this month.
Expected results (on 4/22/16) from the above would be: '05-18-2016', '05-10-2016', '04-28-2016'.
I'd prefer to do it mathematically and avoid creating a calendar table if possible.
Thanks.
Partial answer, which is by no means bug free.
This doesn't cater for 'Every-' entries, but hopefully will give you some inspiration. I'm sure there are plenty of test cases this will fail on, and you might be better off writing a stored proc.
I did try to do this by calculating the day name and day number of the first day of the month, then calculating the next wanted day and applying an offset, but it got messy. I know you said no date table but the CTE simplifies things.
How it works
A CTE creates a calendar for the current month of date and dayname. Some rather suspect parsing code pulls the day name from the test data and joins to the CTE. The where clause filters to dates greater than the Nth occurrence, and the select adds 4 weeks if the date has passed. Or at least that's the theory :)
I'm using DATEFROMPARTS to simplify the code, which is a SQL 2012 function - there are alternatives on SO for 2008.
SELECT * INTO #TEST FROM (VALUES ('3rd-Wednesday'), ('2nd-Tuesday'), ('4th-Monday')) A(Value)
SET DATEFIRST 1
;WITH DAYS AS (
SELECT
CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number) AS DATE) Date,
DATENAME(WEEKDAY, DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number)) DayName
FROM master..spt_values N WHERE N.type = 'P' AND N.number BETWEEN 0 AND 31
)
SELECT
T.Value,
CASE WHEN MIN(D.Date) < GETDATE() THEN DATEADD(WEEK, 4, MIN(D.DATE)) ELSE MIN(D.DATE) END Date
FROM #TEST T
JOIN DAYS D ON REVERSE(SUBSTRING(REVERSE(T.VALUE), 1, CHARINDEX('-', REVERSE(T.VALUE)) -1)) = D.DayName
WHERE D.Date >=
DATEFROMPARTS(
YEAR(GETDATE()),
MONTH(GETDATE()),
1+ 7*(CAST(SUBSTRING(T.Value, 1,1) AS INT) -1)
)
GROUP BY T.Value
Value Date
------------- ----------
2nd-Tuesday 2016-05-10
3rd-Wednesday 2016-05-18
4th-Monday 2016-04-25

SQL Count sickness days

I have a SQL Server table which contains the list of all staff and their sickness.
I need to be able to calculate how many days they have had sick in the current quarter
The issue is, some people may have been sick for a year so, E.G the FROMDATE could be 2013-12-31 and the UNTILDATE could be 2014-12-31 (1 year sickness leave). However it should only count the days from that sickness that occur in the current quarter. So it should be around 90 days of sickness rather than count the entire year.
Current SQL
select SUM(a.WORKDAYS) as Total
from ABSENCE a
where a.FROMDATE < GETDATE() and
a.UNTILDATE > DATEADD(MONTH, -3, GETDATE())
and
a.ABS_REASON='SICK'
So at the moment, it takes from any fromdate which is correct as I need to account for people who were already sick before the quarter started but still sick going into the current quarter but should only count the number of days from when the quarter started until the end of the quarter.
Any help would be greatly appreciated.
With a table of dates, you could easily find the count of dates where the date is between your two dates of interest, and where there exists a leave period that surrounds it. You could also filter your dates to exclude non-business days and public holidays.
There are lots of ways to generate such a table of dates, and plenty described both on stackoverflow and dba.stackexchange.
Not sure about your columns.you should only provide sql that gives records between 2013-12-31 and 2014-12-31 and then ask your problem .
Try this,
select SUM(Case when datepart(MM, a.FROMDATE) IN (10,11,12) Then a.WORKDAYS Else End)
as Total
from ABSENCE a
where a.FROMDATE >= '2013-12-31' and
a.UNTILDATE <= '2014-12-31'
and
a.ABS_REASON='SICK'
SELECT SUM(a.WORKDAYS) as Total
FROM ABSENCE a
WHERE (a.FROMDATE >= DATEADD(MONTH, -3, GETDATE()) OR a.UNTILDATE >= DATEADD(MONTH, -3, GETDATE()))
AND a.ABS_REASON = 'SICK'
Quarter Specific
SELECT SUM(a.WORKDAYS) as Total
FROM ABSENCE a
WHERE (a.FROMDATE >= DATEADD(quarter, -1, GETDATE()) OR a.UNTILDATE >= DATEADD(quarter, -1, GETDATE()))
AND a.ABS_REASON = 'SICK'
Having a Calendar table with the list of all possible dates is handy, but in this case we can do without it.
I'll generalize your question a bit. Instead of looking just at the current quarter let's have two parameters that define the range of dates that you are interested in:
DECLARE #ParamStartDate date;
DECLARE #ParamEndDate date;
At first we need to get all rows from Absence that have a range from FromDate to UntilDate that intersects with the given period.
SELECT
...
FROM
Absence
WHERE
ABS_REASON='SICK'
-- all absence periods, which overlap with the given period
AND FromDate <= #ParamEndDate
AND UntilDate >= #ParamStartDate
Two periods A and B overlap when (StartA <= EndB) and (EndA >= StartB).
Then we need to calculate how many days are in the intersection of the two periods.
The intersection period can't be larger than the given range of dates (#ParamStartDate to #ParamEndDate).
The intersection period can't be larger than the duration of the sickness (FromDate to UntilDate).
So, the beginning of the intersection is the latest of FromDate and #ParamStartDate, i.e. MAX(FromDate, #ParamStartDate)
The ending of the intersection is the earliest of UntilDate and #ParamEndDate, i.e. MIN(UntilDate, #ParamEndDate)
Finally, the duration of the intersection in days is
DATEDIFF(day, MAX(FromDate, #ParamStartDate), MIN(UntilDate, #ParamEndDate))
But, only if it is positive. If it is negative, it means that sickness period ended before the given quarter started (or sickness started after the given quarter ended).
There is no built-in MIN, MAX functions that take two parameters as I need, so I use CROSS APPLY to calculate them. Also, I calculate the number of days in the given quarter, just for completeness. The final query looks like this:
SELECT
1+DATEDIFF(day, #ParamStartDate, #ParamEndDate) AS QuarterDays
,CASE WHEN 1+DATEDIFF(day, CTE_MaxStartDate.AbsenceStartDate, CTE_MinEndDate.AbsenceEndDate) > 0
THEN 1+DATEDIFF(day, CTE_MaxStartDate.AbsenceStartDate, CTE_MinEndDate.AbsenceEndDate)
ELSE 0 END AS AbsenceDays
FROM
Absence
CROSS APPLY
(
SELECT CASE WHEN UntilDate < #ParamEndDate THEN UntilDate ELSE #ParamEndDate END AS AbsenceEndDate
) AS CTE_MinEndDate
CROSS APPLY
(
SELECT CASE WHEN FromDate > #ParamStartDate THEN FromDate ELSE #ParamStartDate END AS AbsenceStartDate
) AS CTE_MaxStartDate
WHERE
ABS_REASON='SICK'
-- all absence periods, which overlap with the given period
AND FromDate <= #ParamEndDate
AND UntilDate >= #ParamStartDate
I add 1 to DATEDIFF to get a duration of one day if start and end dates of the period are the same.