简体   繁体   中英

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM