Get date range between dates having only month and day every year - sql

I need to get date range between 1st July till 31st October every year. Based on that I have to update another column.
Date field is datetime. Should be like this below:
Select Cash = Case When date between '1st July' and '31st October' Then (Cash * 2) End
From MytTable
Note: this range should work for each and every year.

This is one way:
SELECT Cash = CASE WHEN RIGHT(CONVERT(VARCHAR(8),[date],112),4)
BETWEEN '0701' AND '1031' THEN Cash*2
ELSE Cash END --I added this

For your case you could just use the month and make sure it falls between 7 to 10.
This is how your query will be:
select Cash = case when month([Date]) in (7, 8, 9, 10) then (Cash * 2) else Cash end
or
select Cash = case when month([Date]) between 7 and 10 then (Cash * 2) else Cash end

This solution worked for me to get some themes that have been recurring scheduled by month and day. It handles spanning multiple years for example an item scheduled from Nov 1 to March 1.
My data is stored like this:
ThemeName, StartMonth, StartDay, EndMonth, EndDay
Christmas, 12,1,12,26
Winter, 11, 1, 3, 1
Spring, 3,1,5,1
Sports, 0,0,0,0 //continuous
Here is the query:
SELECT
DISTINCT ThemeName
FROM (
SELECT
CASE WHEN EndDate < StartDate AND ((Current Between StartDate AND 1231) OR (Current Between 0101 AND EndDate) ) THEN -- span multiple year between dates
ThemeName
WHEN StartDate < EndDate AND Current Between StartDate AND EndDate THEN -- normal between dates
ThemeName
WHEN StartDate = 0 AND EndDate = 0 THEN -- continuous
ThemeName
ELSE
'NG'
END as ThemeName
FROM (
SELECT
ThemeName, StartDate, EndDate, Current
FROM (
SELECT
ThemeName,
CAST(Concat(LPAD(CONVERT(StartMonth , CHAR(2)),2,'0'), LPAD(CONVERT(StartDay , CHAR(2)),2,'0') ) as DECIMAL) as StartDate,
CAST(Concat(LPAD(CONVERT(EndMonth , CHAR(2)),2,'0'), LPAD(CONVERT(EndDay , CHAR(2)),2,'0') ) as DECIMAL) as EndDate,
CAST(Concat(LPAD(CONVERT(MONTH(CURRENT_DATE) , CHAR(2)),2,'0'), LPAD(CONVERT(DAY(CURRENT_DATE) , CHAR(2)),2,'0') ) as DECIMAL) as current
FROM
companythemeschedules
WHERE
companyid = 221
) a
) b
) c
WHERE
ThemeName != 'NG'

TRY THIS ONE:
DECLARE #DT DATE = GETUTCDATE() , #START_DATE VARCHAR(10) = '07-01' , #END_DATE VARCHAR(10) = '10-31'
SELECT
CASE
WHEN RIGHT(#DT,5) BETWEEN #START_DATE AND #END_DATE
THEN
(CASH * 2)
ELSE
'NO MATCH'
END AS CASH

Related

How to get sets of date divided by week on the basis of present date and First day of month

for example
if today is 22 Jan ,then i want get the date set as
(StartDate :16-Jan-2018 & EndDate:22-Jan-2018)
and
second date set is as
(StartDate :9-Jan-2018 & EndDate:15-Jan-2018)
and
third date set as
(StartDate :02-Jan-2018 & EndDate:08-Jan-2018)
and
last date set as
(StartDate :1-Jan-2018 & EndDate:1-Jan-2018)
where first date will be given by user i.e :22-Jan-2018
Try This
DECLARE #First DATE = '01/22/2018'
;WITH CTE
AS
(
SELECT
SeqNo = 1,
StartDt = CAST(DATEADD(DD,-6,#First) AS DATE),
EndDt = CAST(#First AS DATE)
UNION ALL
SELECT
SeqNo = SeqNo+1,
StartDt = CASE WHEN DAY(CAST(DATEADD(DD,-1,StartDt) AS DATE)) = 1
THEN CAST(DATEADD(DD,-1,StartDt) AS DATE)
ELSE CAST(DATEADD(DD,-7,StartDt) AS DATE) END,
EndDt = CAST(DATEADD(DD,-1,StartDt) AS DATE)
FROM CTE
WHERE SeqNo < 4
)
SELECT
*
FROM CTE
My Result

If/Then/Else in Formatting a Date

I am trying to get the fiscal period and year out of an invoice date. Using the month() function together with the Case I am able to get the period. since Period 1 is in November I need to do a +1 1 the year when this is true
Using the IF function together with the date functions are now working for me.
My query is
Select a.OrderAccount
,a.InvoiceAccount
,a.InvoiceDate
,year(a.InvoiceDate) as Year
,month(a.InvoiceDate) as Month,
Case month(a.InvoiceDate)
WHEN '11' THEN '1' -- increase year by +1
WHEN '12' THEN '2'-- increase year by +1
WHEN '1' THEN '3'
WHEN '2' THEN '4'
WHEN '3' THEN '5'
Any advice would be appreciated. Thanks
Use DATEADD to just add 2 months to the original date:
MONTH(DATEADD(month,2,a.InvoiceDate)) as FiscalMonth,
YEAR(DATEADD(month,2,a.InvoiceDate)) AS FiscalYear,
Create and populate a Calendar Table (it makes working with dates much easier).
create table Calendar
(
id int primary key identity,
[date] datetime,
[day] as datepart(day, [date]) persisted,
[month] as datepart(month, [date]) persisted,
[year] as datepart(year, [date]) persisted,
day_of_year as datepart(dayofyear, [date]) persisted,
[week] as datepart(week, [date]),
day_name as datename(dw, [date]),
is_weekend as case when datepart(dw, [date]) = 7 or datepart(dw, [date]) = 1 then 1 else 0 end,
[quarter] as datepart(quarter, [date]) persisted
--etc...
)
--populate the calendar
declare #date datetime
set #date = '1-1-2000'
while #date <= '12-31-2100'
begin
insert Calendar select #date
set #date = dateadd(day, 1, #date)
end
Then, create a FiscalYear view:
create view FiscalYear
as
select
id,
case when month = 11 or month = 12 then year + 1 else year end as [year]
from Calendar
So, whenever you need the fiscal year of a given date, just use something like the following query:
select C.*, FY.year fiscal_year from Calendar C inner join FiscalYear FY on FY.id = C.id
Of course, since fiscal year is just a computation on a column, you could also just make it a part of the calendar table itself. Then, it's simply:
select * from Calendar
If you want to stick with arithmetic: The fiscal month is ( Month( a.InvoiceDate ) + 1 ) % 12 + 1 and the value to add to the calendar year to get the fiscal year is Month( a.InvoiceDate ) / 11.
The following code demonstrates 12 months:
with Months as (
select 1 as M
union all
select M + 1
from Months
where M < 12 )
select M, ( M + 1 ) % 12 + 1 as FM, M / 11 as FYOffset
from Months;
D Stanley's answer makes your intention clearer, always a consideration for maintainability.
If you have this logic in 10 different places and the logic changes starting (say) on 1/1/2018 you will have a mess on your hands.
Create a function that has the logic and then use the function like:
SELECT InvoiceDate, dbo.FiscalPeriod(InvoiceDate) AS FP
FROM ...
Something like:
CREATE FUNCTION dbo.FiscalPeriod(#InvoiceDate DateTime)
RETURNS int
AS BEGIN
DECLARE #FiscalDate DateTime
SET #FiscalDate = DATEADD(month, 2, #InvoiceDate)
RETURN YEAR(#FiscalDate) * 100 + MONTH(#FiscalDate)
END
This returns values like 201705, but you could have dbo.FiscalPeriodMonth() and dbo.FiscalPeriodYear() if you needed. And you can have as complicated logic as you need in one place.

difference between two dates without weekends and holidays Sql query ORACLE

I have 2 tables: the 1st one contains the start date and the end date of a purchase order,
and the 2nd table contains year hollidays
-purchase order
-Holidays
I'm tryign to calculate the number of business days between 2 dates without the weekends and the holidays.
the output should be like this:
Start Date | End Date | Business Days
Could you please help me
You can remove the non-weekend holidays with a query like this:
select (t.end_date - t.start_date) - count(c.date)
from table1 t left join
calendar c
on c.date between t1.start_date and t1.end_date and
to_char(c.date, 'D') not in ('1', '7')
group by t.end_date, t.start_date;
Removing the weekend days is then more complication. Full weeks have two weekend days, so that is easy. So a good approximation is:
select (t.end_date - t.start_date) - (count(c.date) +
2 * floor((t.end_date - t.start_date) / 7))
from table1 t left join
calendar c
on c.date between t1.start_date and t1.end_date and
to_char(c.date, 'D') not in ('1', '7')
group by t.end_date, t.start_date;
This doesn't get the day of week, which is essentially if the end date is before the start date, then it is in the following week. However, this logic gets rather complicated the way that Oracle handles day of the week, so perhaps the above approximation is sufficient.
EDIT: I ignored the presence of the Oracle tag and jumped into scripting this for SQL Server. The concept doesn't change though.
To be super accurate, I would create a table whit the following format.
Year int, month int, DaysInMonth int, firstOccuranceOfSunday int
Create a Procedure to extract the weekends from a specific year and month on that table.
CREATE FUNCTION [dbo].[GetWeekendsForMonthYear]
(
#year int,
#month int
)
RETURNS #weekends TABLE
(
[Weekend] date
)
AS
BEGIN
declare #firstsunday int = 0
Declare #DaysInMonth int = 0
Select #DaysInMonth = DaysInMonth, #firstsunday = FirstSunday from Months
Where [Year] = #year and [month] = #month
Declare #FirstSaterday int = #firstsunday - 1
declare #CurrentDay int = 0
Declare #CurrentDayIsSunday bit = 0
if #FirstSaterday !< 1
Begin
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #Firstsaterday -1, 0))))
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #FirstSunday -1, 0))))
set #CurrentDayIsSunday = 1
set #CurrentDay = #firstsunday
END
else
begin
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #FirstSunday -1, 0))))
set #FirstSaterday = #firstsunday + 6
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #Firstsaterday -1, 0))))
set #CurrentDayIsSunday = 0
set #CurrentDay = #FirstSaterday
end
declare #done bit = 0
while #done = 0
Begin
if #CurrentDay <= #DaysInMonth
Begin
If #CurrentDayIsSunday = 1
begin
set #CurrentDay = #CurrentDay + 6
set #CurrentDayIsSunday = 0
if #CurrentDay <= #DaysInMonth
begin
insert into #Weekends Values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #CurrentDay -1, 0))))
end
end
else
begin
set #CurrentDay = #CurrentDay + 1
set #CurrentDayIsSunday = 1
if #CurrentDay <= #DaysInMonth
begin
insert into #Weekends Values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #CurrentDay -1, 0))))
end
end
end
ELSE
begin
Set #done = 1
end
end
RETURN
END
When called and provided with a year and month this will return a list of dates that represent weekends.
Now, using that function, create a procedure to call this function once for every applicable row in a specific date rang and return the values in a temptable.
Note, I'm posting this now so you can see what's going on but I am continuing to work on the code. I will post updates as they arise.
More to come: Get list of weekends(formatted) for a specific daterange, remove any dates from that list which can be found on your holidays table.
Unfortunately, I have to work tomorrow and am off to bed.
This query should produce exact number of business days for each range in purchase table:
with days as (
select rn, sd + level - 1 dt, sd, ed
from (select row_number() over (order by start_date) rn,
start_date sd, end_date ed from purchase_order)
connect by prior rn = rn and sd + level - 1 <= ed
and prior dbms_random.value is not null)
select sd start_date, ed end_date, count(1) business_days
from days d left join holidays h on holiday_date = d.dt
where dt - trunc(dt, 'iw') not in (5, 6) and h.holiday_date is null
group by rn, sd, ed
SQLFiddle demo
For each row in purchase_orders query generates dates from this range (this is done by subquery dates).
Main query checks if this is weekend day or holiday day and counts rest of dates.
Hierarchical query used to generate dates may cause slowdowns if there is big number of data in purchase_orders
or periods are long. In this case preferred way is to create calendar table, as already suggested in comments.
Since you already have a table of holidays you can count the holidays between the starting and ending date and subtract that from the difference in days between your ending and starting date. For the weekends, you either need a table containing weekend days similar to your table of holidays, or you can generate them as below.
with sample_data(id, start_date, end_date) as (
select 1, date '2015-03-06', date '2015-03-7' from dual union all
select 2, date '2015-03-07', date '2015-03-8' from dual union all
select 3, date '2015-03-08', date '2015-03-9' from dual union all
select 4, date '2015-02-07', date '2015-06-26' from dual union all
select 5, date '2015-04-17', date '2015-08-16' from dual
)
, holidays(holiday) as (
select date '2015-01-01' from dual union all -- New Years
select date '2015-01-19' from dual union all -- MLK Day
select date '2015-02-16' from dual union all -- Presidents Day
select date '2015-05-25' from dual union all -- Memorial Day
select date '2015-04-03' from dual union all -- Independence Day (Observed)
select date '2015-09-07' from dual union all -- Labor Day
select date '2015-11-11' from dual union all -- Veterans Day
select date '2015-11-26' from dual union all -- Thanks Giving
select date '2015-11-27' from dual union all -- Black Friday
select date '2015-12-25' from dual -- Christmas
)
-- If your calendar table doesn't already hold weekends you can generate
-- the weekends with these next two subfactored queries (common table Expressions)
, firstweekend(weekend, end_date) as (
select next_day(min(start_date),'saturday'), max(end_date) from sample_data
union all
select next_day(min(start_date),'sunday'), max(end_date) from sample_data
)
, weekends(weekend, last_end_date) as (
select weekend, end_date from firstweekend
union all
select weekend + 7, last_end_date from weekends where weekend+7 <= last_end_date
)
-- if not already in the same table combine distinct weekend an holiday days
-- to prevent double counting (in case a holiday is also a weekend).
, days_off(day_off) as (
select weekend from weekends
union
select holiday from holidays
)
select id
, start_date
, end_date
, end_date - start_date + 1
- (select count(*) from days_off where day_off between start_date and end_date) business_days
from sample_data;
ID START_DATE END_DATE BUSINESS_DAYS
---------- ----------- ----------- -------------
1 06-MAR-2015 07-MAR-2015 1
2 07-MAR-2015 08-MAR-2015 0
3 08-MAR-2015 09-MAR-2015 1
4 07-FEB-2015 26-JUN-2015 98
5 17-APR-2015 16-AUG-2015 85

Possible recursive CTE query using date ranges

Not sure how to even phrase the title on this one!
I have the following data:
IF OBJECT_ID ('tempdb..#data') IS NOT NULL DROP TABLE #data
CREATE TABLE #data
(
id UNIQUEIDENTIFIER
,reference NVARCHAR(30)
,start_date DATETIME
,end_date DATETIME
,lapse_date DATETIME
,value_received DECIMAL(18,3)
)
INSERT INTO #data VALUES ('BE91B9C1-C02F-46F7-9B63-4D0B25D9BA2F','168780','2006-05-01 00:00:00.000',NULL,'2011-09-27 00:00:00.000',537.42)
INSERT INTO #data VALUES ('B538F123-C839-447A-B300-5D16EACF4560','320858','2011-08-08 00:00:00.000',NULL,NULL,0)
INSERT INTO #data VALUES ('1922465D-2A55-434D-BAAA-8E15D681CF12','306597','2011-04-08 00:00:00.000','2011-06-22 13:14:40.083','2011-08-07 00:00:00.000',12)
INSERT INTO #data VALUES ('7DF8FBCC-B490-4892-BDC5-8FD2D73B0323','321461','2011-07-01 00:00:00.000',NULL,'2011-09-25 00:00:00.000',8.44)
INSERT INTO #data VALUES ('1EC2E754-F325-4313-BDFC-9010E255F6FE','74215','2000-10-31 00:00:00.000',NULL,'2011-08-30 00:00:00.000',258)
INSERT INTO #data VALUES ('9E59B09C-0198-48AC-8EEC-A0D76CEA9385','169194','2008-06-25 00:00:00.000',NULL,'2011-09-25 00:00:00.000',1766.4)
INSERT INTO #data VALUES ('97CF6C0F-324A-49A6-B9D8-AC848A1F821A','288039','2010-09-01 00:00:00.000','2011-07-29 00:00:00.000','2011-08-21 00:00:00.000',55)
INSERT INTO #data VALUES ('97CF6C0F-324A-49A6-B9D8-AC848A1F821A','324423','2011-08-01 00:00:00.000',NULL,'2011-09-25 00:00:00.000',5)
INSERT INTO #data VALUES ('D5E5197A-E8E1-468C-9991-C8712224C2BF','323395','2011-08-25 00:00:00.000',NULL,NULL,0)
INSERT INTO #data VALUES ('0EC4976C-16B9-4C99-BD07-D0CBDF014D32','323741','2011-08-25 00:00:00.000',NULL,NULL,0)
And I want to be able to group all references into a category of 'active', 'lapsed' or 'new' based upon the following criteria:
Active has a start date that is less than the last date of the reference month, a lapse date after the last day of the prior month and a value_received > 0;
New has a start date which falls within the reference month;
Lapsed has a lapse date which falls within the reference month.
And to then apply these definitions for each reference for a rolling 13 months (so from Now going back as far as July 2010) so that for each month I can see how many references fall into each group.
I am able to use the following to define this for the current month:
select
id
,reference
,start_date
,end_date
,lapse_date
,value_received
,CASE WHEN start_date < DATEADD(month,DATEPART(Month,GETDATE()) + 1,DATEADD(year,DATEPART(year,GETDATE())-1900,0)) --next month start date
AND lapse_date > DATEADD(ms,-3,DATEADD(mm,DATEDIFF(mm,0,GETDATE())+1,0)) --last day of current month
AND value_received > 0
THEN 'Active'
WHEN lapse_date < DATEADD(month,DATEPART(Month,GETDATE()) + 1,DATEADD(year,DATEPART(year,GETDATE())-1900,0)) --next month start
AND lapse_date > DATEADD(ms,-3,DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)) --last day of prior month
THEN 'lapse'
WHEN start_date < DATEADD(month,DATEPART(Month,GETDATE()) + 1,DATEADD(year,DATEPART(year,GETDATE())-1900,0)) --next month start date
AND start_date > DATEADD(ms,-3,DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)) --last day of prior month
THEN 'New'
ELSE 'Not applicable'
END AS [type]
from #data
But I can't see a nice / efficient way of doing this (other than to repeat this query 13 times and union the results, which I know is just awful)
Would this be a case for using the current month as an anchor and using recursion (if so, some pointers would be most appreciated)?
Any help most appreciated as always :)
* Edited to include actual solution *
In case it's of interest to anyone, this is the final query I used:
;WITH Months as
(
SELECT DATEADD(ms,-3,DATEADD(mm,DATEDIFF(mm,0,GETDATE())+1,0)) as month_end
,0 AS level
UNION ALL
SELECT DATEADD(month, -1, month_end)as month_end
,level + 1 FROM Months
WHERE level < 13
)
SELECT
DATENAME(Month,month_end) + ' ' + DATENAME(YEAR,month_end) as date
,SUM(CASE WHEN start_date <= month_end
AND Month(start_date) <> MONTH(Month_end)
AND lapse_date > Month_end
THEN 1 ELSE 0 END) AS Active
,SUM(CASE WHEN start_date <= Month_end
AND DATENAME(MONTH,start_date) + ' ' + DATENAME(YEAR,start_date) =
DATENAME(MONTH,month_end) + ' ' + DATENAME(YEAR,month_end)
THEN 1 ELSE 0 END) AS New
,SUM(CASE WHEN lapse_date <= Month_end
AND Month(lapse_date) = MONTH(Month_end)
THEN 1 ELSE 0 END) AS lapse
FROM #data
CROSS JOIN Months
WHERE id IS NOT NULL
AND start_date IS NOT NULL
GROUP BY DATENAME(Month,month_end) + ' ' + DATENAME(YEAR,month_end)
ORDER by MAX(level) ASC
You don't need a "real" recursive CTE here. You can use one for the month references though:
;WITH Months
as
(
SELECT DATEADD(day, -DATEPART(day, GETDATE())+1, GETDATE()) as 'MonthStart'
UNION ALL
SELECT DATEADD(month, -1, MonthStart) as 'MonthStart'
FROM Months
)
Then you can JOIN to SELECT TOP 13 * FROM Months in your above query.
I'm not going to try to parse all your CASE statements, but essentially you can use a GROUP BY on the date and the MonthStart fields, like:
GROUP BY Datepart(year, monthstart), Datepart(month, monthstart)
and aggregate by month. It will probably be easiest to have all your options (active, lapsed, etc) as columns and calculate each with a SUM(CASE WHEN ... THEN 1 ELSE 0 END) as it will be easier with a GROUP BY.
You can cross join your request with a recursive CTE, this is a good idea.
WITH thirteenMonthBack(myDate, level) as
(
SELECT GETDATE() as myDate, 0 as level
UNION ALL
SELECT DATEADD(month, -1, myDate), level + 1
FROM thirteenMonthBack
WHERE level < 13
)
SELECT xxx
FROM youQuery
CROSS JOIN thirteenMonthBack
DECLARE #date DATE = GETDATE()
;WITH MonthsCTE AS (
SELECT 1 [Month], DATEADD(DAY, -DATEPART(DAY, #date)+1, #date) as 'MonthStart'
UNION ALL
SELECT [Month] + 1, DATEADD(MONTH, 1, MonthStart)
FROM MonthsCTE
WHERE [Month] < 12 )
SELECT * FROM MonthsCTE
/*
| The below SELECT statements show TWO examples of how this can be useful.
| Example 1 SELECT: Simple example of showing how to generate 12 days ahead based on date entered
| Example 2 SELECT: This example shows how to generate 12 months ahead based on date entered
| This example tries to mimic as best it can Oracles use of LEVEL and CONNECT BY LEVEL
*/
WITH dynamicRecords(myDate, level) AS
(
SELECT GETDATE() AS myDate, 1 AS level
UNION ALL
SELECT myDate + 1, level + 1 /* 12 Days - WHERE level < 12 */
--SELECT DATEADD(month, 1, myDate), level + 1 /* 12 Months - WHERE level < 12 */
FROM dynamicRecords
WHERE level < 12
)
SELECT *
FROM dynamicRecords
Option (MaxRecursion 0) /* The default MaxRecursion setting is 100. Generating more than 100 dates using this method will require the Option (MaxRecursion N) segment of the query, where N is the desired MaxRecursion setting. Setting this to 0 will remove the MaxRecursion limitation altogether */
Screenshots:
/* Original T-SQL Solution I found here: https://riptutorial.com/sql-server/example/11098/generating-date-range-with-recursive-cte
| The below provides an example of how to generate the days within a date range of the dates entered.
| The below SELECT statements show TWO examples of how this can be useful.
| Example 1 SELECT: Uses static dates to display ALL of the dates within the range for the dates entered
| Example 2 SELECT: This example uses GETDATE() and then obtains the FOM day and the EOM day of the dates
| beging entered to then show all days in the month of the dates entered.
*/
With DateCte AS
(
SELECT CAST('2021-04-21' AS DATE) AS BeginDate, CAST('2022-05-02' AS DATE) AS EndDate
--SELECT CAST( GETDATE() - Day(GETDATE()) + 1 AS DATE ) AS BeginDate, CAST(EOMONTH(GETDATE()) AS DATE) AS EndDate
UNION ALL
SELECT DateAdd(Day, 1, BeginDate), EndDate
FROM DateCte
WHERE BeginDate < EndDate
)
Select BeginDate AS Dates
From DateCte
Option (MaxRecursion 0) /* The default MaxRecursion setting is 100. Generating more than 100 dates using this method will require the Option (MaxRecursion N) segment of the query, where N is the desired MaxRecursion setting. Setting this to 0 will remove the MaxRecursion limitation altogether */
;
Screenshot:

simulating Excel networkdays in sql

I am sure somebody has worked out to simulate Excel networkdays function in other programming language.
I'd appreciate if you could share.
Thanks
The idea is to calculate the weekdays between the start of each date's week then applying various offsets.
Find the number of days between the Saturday before each date
Divide by 7 and multiply by 5 to get the number of weekdays
Offset the total for whether the start date is after the end date
Offset again for whether the start is after the end and the start is a Saturday
Again for whether the start is after and the end is Sunday
Again for whether the start is not after and the start is a Sunday
Again for whether the start is not after and the end is a Saturday
Add some random dates into a table.
declare #t table ([start] datetime, [end] datetime)
insert into #t values ('2088-01-14 11:56:23','2011-11-10 03:34:09')
insert into #t values ('2024-09-24 10:14:29','2087-09-16 15:52:06')
Then calcuate NETWORKDAYS for those dates.
select [start],[end]
,((datediff(day,0,[end])-datepart(dw,[end]))-(datediff(day,0,[start])-datepart(dw,[start])))/7*5 --[weekdays]
+ datepart(dw,[end]) - datepart(dw,[start]) --[weekday diff]
+ case when datediff(day,0,[start]) > datediff(day,0,[end]) then -1 else 1 end --[start after]
+ case when datediff(day,0,[start]) > datediff(day,0,[end]) and datepart(dw,[start]) = 7 then 1 else 0 end --[start after and start saturday]
+ case when datediff(day,0,[start]) > datediff(day,0,[end]) and datepart(dw,[end]) = 1 then 1 else 0 end --[start after and end sunday]
+ case when datediff(day,0,[start]) <= datediff(day,0,[end]) and datepart(dw,[start]) = 1 then -1 else 0 end --[start not after and start sunday]
+ case when datediff(day,0,[start]) <= datediff(day,0,[end]) and datepart(dw,[end]) = 7 then -1 else 0 end --[start not after and end saturday]
as [networkdays]
from #t
A SQL solution as per SO question "Equivalent of Excel’s NETWORKDAYS function with Jet ADO"
Hope this helps someone out there but this works for me. Requires you to create a dbo.FederalHolidays table (which can be easily populated with online date sources).
Cleanest way I could come up with that is easily scalable.
--Simulate Excel Formula =NETWORKDAYS() excludes weekend days and federal holidays. Requires a Federal Holiday Table, easy to create.
DECLARE #d1 date, #d2 date
SET #d1 = '4/12/2019'
SET #d2 = '4/23/2019'
SELECT
DATEDIFF(dd,#d1,#d2) +1 --First calculate regular date difference +1 to count the start date; does not exclude weekends/holidays.
- (SELECT COUNT(*) --Calculate number of holidays between the date range and subtract it from DATEDIFF().
FROM [dbo].[FederalHolidays]
WHERE DATE BETWEEN #d1 AND #d2)
- (SELECT (DATEDIFF(wk, #d1, #d2) * 2) --Calculate number of weekend days between the date range and subtract it from DATEDIFF().
+(CASE WHEN DATENAME(dw, #d1) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, #d2) = 'Saturday' THEN 1 ELSE 0 END)) as NetWorkDays
For what it's worth, I created the following function for mysql that assumes Mon-Fri are business days:
DROP FUNCTION IF EXISTS BusinessDays;
DELIMITER //
CREATE FUNCTION BusinessDays (startDate DATE, endDate DATE)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE startWeekDay INT;
DECLARE allDays INT;
DECLARE fullWeekCount INT;
DECLARE remainderDays INT;
DECLARE maxPossibleRemainderWeekendDays INT;
DECLARE soloSundays INT;
DECLARE totalBusinessDays INT;
SET startWeekDay = WEEKDAY(startDate);
SET allDays = ABS(DATEDIFF(endDate, startDate)) + 1;
SET fullWeekCount = FLOOR(allDays/7);
SET remainderDays = allDays - (fullWeekCount * 7);
SET maxPossibleRemainderWeekendDays = ROUND(2*(startWeekDay+remainderDays-6)/(ABS(2*(startWeekDay+remainderDays-6))+1))+1;
SET soloSundays = ROUND(2*(startWeekDay-6)/(ABS(2*(startWeekDay-6))+1))+1;
SET totalBusinessDays = allDays - (fullWeekCount * 2) - maxPossibleRemainderWeekendDays + soloSundays;
RETURN totalBusinessDays;
END //
DELIMITER ;
Perhaps this will also help, I created a formula (in Excel) that will simulate the NETWORKDAYS function:
= 1 + ( ( B1 - A1) * 5 - ( WEEKDAY(A1) - WEEKDAY(B1) ) * 2 ) / 7 + IF(A1<=B1,IF(WEEKDAY(B1)=7,-1,0) + IF(WEEKDAY(A1)=1,-1,0), IF(WEEKDAY(B1)<>1,-1,0) + IF(WEEKDAY(A1)<>7,-1,0) )
NOTE: WEEKDAY() has Sunday as 0 to Saturday as 6
I have been looking for this capability for quite some time, so went ahead and just created it on my own.
Usage: This is a function you can create in SQL. You can easily modify this to work outside a function if needed, but I prefer functions to take care of these types of calculations.
I added quite a few comments in case anyone was wondering how it worked. What this does is take two inputs:
#startDate - the beginning date you want to add workday to.
#addDays - the whole number of days you want to add to the #startDate.
Calculation process:
It loops repeatedly until the # of working days has been reached. For example, if you enter 365 days, it will cycle around 500 times before it's able to predict the # of working days to add. I've added #weekendCount in case anyone needs to know the # of weekend days excluded before reaching the final end date.
Once completed, the integer #recCounter essentially is the # of days that must be added to the #startDate before the number of working days is reached. I am sure someone can write this better than I, or perhaps someone can make use of this. Hope this helps! :)
CREATE FUNCTION [dbo].[addNetworkDays](#startDate AS DATETIME, #addDays AS Int)
RETURNS DATETIME
AS
BEGIN
DECLARE #recCounter Int
SET #recCounter = 0
DECLARE #weekendCount Int
SET #weekendCount = 0
DECLARE #workdayCount Int
SET #workdayCount = 0
DECLARE #newDate DateTime
WHILE #addDays > #workdayCount
BEGIN
-- Add another day to the start date
SET #recCounter = #recCounter + 1
-- Cumuluate the weekend vs. workday counter, based on the day of the week. This loop will repeat until #workdayCount has reached the #addDays.
-- Note that #weekendCount is not used in any capacity, can be used if you need to know how many weekend days there are.
IF DATEPART(dw, DATEADD(d, #recCounter, #startDate)) IN (1, 7) SET #weekendCount = #weekendCount + 1
IF DATEPART(dw, DATEADD(d, #recCounter, #startDate)) IN (2, 3, 4, 5, 6) SET #workdayCount = #workdayCount + 1
END
-- At this point, the script has completed the cycle, stopping when the detected # of workdays has reached the count of days the user specified.
-- Calculate the new date, based on the # of cycles *days* detected above.
SET #newDate = DATEADD(d, #recCounter, #startDate)
-- If the new/adjusted date falls on a Saturday or Sunday, add additional days to compensate.
IF DATEPART(dw, #newDate) = 1 SET #newDate = DATEADD(d, 1, #newDate)
IF DATEPART(dw, #newDate) = 7 SET #newDate = DATEADD(d, 1, #newDate)
RETURN CAST(#newDate AS DATETIME)
NETWORKDAYS for Australia
WITH PH_NET_WORKDAY AS
(
----BUILD UNDERLYING WORKDAY DATA TABLE
SELECT
DT,
STATE,
CASE
WHEN WORKDAY-PUBLIC_HOLIDAY <0
THEN 0
ELSE WORKDAY-PUBLIC_HOLIDAY
END AS WORKDAY
FROM
(
SELECT
DT,
STATE,
WORKDAY,
----PUBLIC HOLIDAY INFORMATION HERE
CASE
WHEN STATE = 'NSW'
THEN
CASE
WHEN DT IN (
'01-Oct-2018',
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'19-Apr-2019',
'20-Apr-2019',
'21-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'07-Oct-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'SA'
THEN
CASE
WHEN DT IN (
'01-Oct-2018',
'24-Dec-2018',
'25-Dec-2018',
'26-Dec-2018',
'31-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'11-Mar-2019',
'19-Apr-2019',
'20-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'07-Oct-2019',
'24-Dec-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'QLD'
THEN
CASE
WHEN DT IN (
'01-Oct-2018',
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'19-Apr-2019',
'20-Apr-2019',
'21-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'06-May-2019',
'07-Oct-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'VIC'
THEN
CASE
WHEN DT IN (
'28-Sep-2018',
'06-Nov-2018',
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'11-Mar-2019',
'19-Apr-2019',
'20-Apr-2019',
'21-Apr-2019',
'22-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'05-Nov-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
WHEN STATE = 'TAS'
THEN
CASE
WHEN DT IN (
'25-Dec-2018',
'26-Dec-2018',
'01-Jan-2019',
'28-Jan-2019',
'11-Mar-2019',
'19-Apr-2019',
'22-Apr-2019',
'23-Apr-2019',
'25-Apr-2019',
'10-Jun-2019',
'25-Dec-2019',
'26-Dec-2019'
)
THEN 1
ELSE 0
END
ELSE 0
END AS PUBLIC_HOLIDAY
FROM
(
SELECT
DT.*,
ST.*
FROM
(
SELECT
TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) AS DT,
TRIM(TO_CHAR( TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) , 'DAY')) AS WEEK_DAY,
CASE
WHEN TRIM(TO_CHAR( TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) , 'DAY')) = 'SATURDAY'
THEN 0
WHEN TRIM(TO_CHAR( TRUNC (TO_DATE('01-JAN-2021','DD-MON-YYYY') - ROWNUM) , 'DAY')) = 'SUNDAY'
THEN 0
----BUILD STATE PUBLIC HOLIDAY DATE SET HERE, WE CAN EXCLUDE PUBLIC HOLIDAYS
ELSE 1
END AS WORKDAY
FROM DUAL CONNECT BY ROWNUM < (365.25*8+1)
) DT
,
(
SELECT 'NSW' AS STATE FROM DUAL
UNION
SELECT 'QLD' AS STATE FROM DUAL
UNION
SELECT 'SA' AS STATE FROM DUAL
UNION
SELECT 'ACT' AS STATE FROM DUAL
UNION
SELECT 'VIC' AS STATE FROM DUAL
UNION
SELECT 'ACT' AS STATE FROM DUAL
) ST
)
)
),
----A SAMPLE DATA SET FOR SAME DATES BETWEEN DIFFERENT STATE TO TEST PUBLIC HOLIDAY DIFFERENCES
SAMPLE_DATA AS
(
SELECT
'01-FEB-2019' AS D1,
'11-FEB-2019' AS D2,
'NSW' AS STATE
FROM DUAL
UNION
SELECT
'01-FEB-2019' AS D1,
'11-FEB-2019' AS D2,
'SA' AS STATE
FROM DUAL
UNION
SELECT
'19-APR-2019' AS D1,
'26-APR-2019' AS D2,
'NSW' AS STATE
FROM DUAL
)
----SELECT WORKDAYS FROM PH TABLE AND INSERT INTO SAPLE TABLE
SELECT
SAMPLE_DATA.*,
(
SELECT SUM(WORKDAY)
FROM PH_NET_WORKDAY
WHERE
STATE = SAMPLE_DATA.STATE
AND DT>=SAMPLE_DATA.D1
AND DT<=SAMPLE_DATA.D2
)
AS WORKDAYS
FROM SAMPLE_DATA