根据Oracle SQL中的营业时间计算小时数

时间:2017-01-30 12:50:42

标签: sql oracle

我希望根据营业时间计算任务开始和结束之间的小时数。我有以下示例数据:

export function toggleDrawer (args: EventData) {
    //this way
    let page = <Page>(<any>args.object).page;
    //or this way
    let page = frameModule.topmost();
    //at last get drawer content at page
    let drawer: any = page.getViewById("sideDrawer");
    drawer.toggleDrawerState();
};

我需要根据以下营业时间计算两者之间的差异:

TASK | START_TIME | END_TIME
A | 16-JAN-17 10:00 | 23-JAN-17 11:35
B | 18-JAN-17 17:53 | 19-JAN-17 08:00
C | 13-JAN-17 13:00 | 17-JAN-17 14:52
D | 21-JAN-17 10:00 | 30-JAN-17 08:52

我知道如何编写计算但不知道如何将营业时间添加到计算中。

任何建议都将受到赞赏。

2 个答案:

答案 0 :(得分:0)

使用相关的分层查询为每个工作日生成一行,然后将每天的小时数相加:

SELECT task,
       COALESCE( SUM( end_time - start_time ), 0 ) * 24 AS total_hours
FROM   (
  SELECT task,
         GREATEST( t.start_time, d.column_value + INTERVAL '8' HOUR ) AS start_time,
         LEAST( t.end_time, d.column_value + INTERVAL '18' HOUR ) AS end_time
  FROM   your_table t
         LEFT OUTER JOIN
         TABLE(
           CAST(
             MULTISET(
               SELECT TRUNC( t.start_time + LEVEL - 1 )
               FROM   DUAL
               WHERE  TRUNC( t.start_time + LEVEL - 1 ) - TRUNC( t.start_time + LEVEL - 1, 'iw' ) < 6
               CONNECT BY TRUNC( t.start_time + LEVEL - 1 ) < t.end_time
             ) AS SYS.ODCIDATELIST
           )
         ) d
         ON (   t.end_time   > d.column_value + INTERVAL  '8' HOUR
            AND t.start_time < d.column_value + INTERVAL '18' HOUR )
)
GROUP BY task;

答案 1 :(得分:0)

我最喜欢这个问题是使用内置SCHEDULER SCHEDULE

您必须使用DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING

创建一个功能

首先,如果需要,为公共假日等例外创建一些时间表。 这是美国银行日的一个例子:

BEGIN
    DBMS_SCHEDULER.CREATE_SCHEDULE('NEW_YEARS_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=0101');
    DBMS_SCHEDULER.CREATE_SCHEDULE('MARTIN_LUTHER_KING_DAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=JAN;BYDAY=3 MON', comments => 'Third Monday of January');
    DBMS_SCHEDULER.CREATE_SCHEDULE('WASHINGTONS_BIRTHDAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=FEB;BYDAY=3 MON', comments => 'Third Monday of February');
    DBMS_SCHEDULER.CREATE_SCHEDULE('MEMORIAL_DAY', repeat_interval => 'FREQ=MONTHLY;BYMONTH=MAY;BYDAY=-1 MON', comments => 'Last Monday of May');
    DBMS_SCHEDULER.CREATE_SCHEDULE('INDEPENDENCE_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=0704');
    DBMS_SCHEDULER.CREATE_SCHEDULE('CHRISTMAS_DAY', repeat_interval => 'FREQ=YEARLY;INTERVAL=1;BYDATE=1225');
    DBMS_SCHEDULER.CREATE_SCHEDULE('SPRING_BREAK', repeat_interval => 'FREQ=YEARLY;BYDATE=0301+SPAN:7D');
END;

德国银行日的另一个例子:

BEGIN
    DBMS_SCHEDULER.CREATE_SCHEDULE('New_Year', repeat_interval => 'FREQ=YEARLY;BYDATE=0101');

    DBMS_SCHEDULER.CREATE_SCHEDULE('Easter_Sunday',  repeat_interval => 'FREQ=YEARLY;BYDATE=20150405,    20160327,    20170416,    20170416,    20180401,    20190421,    20200412', comments => 'Hard coded till 2020');
    DBMS_SCHEDULER.CREATE_SCHEDULE('Good_Friday',    repeat_interval => 'FREQ=YEARLY;BYDATE=20150405-2D, 20160327-2D, 20170416-2D, 20170416-2D, 20180401-2D, 20190421-2D, 20200412-2D', comments => '2 Days before Easter');
    DBMS_SCHEDULER.CREATE_SCHEDULE('Easter_Monday',   repeat_interval => 'FREQ=YEARLY;BYDATE=20150405+1D, 20160327+1D, 20170416+1D, 20170416+1D, 20180401+1D, 20190421+1D, 20200412+1D', comments => '1 Day after Easter');
    DBMS_SCHEDULER.CREATE_SCHEDULE('Ascension_Day',   repeat_interval => 'FREQ=YEARLY;BYDATE=20150405+39D,20160327+39D,20170416+39D,20170416+39D,20180401+39D,20190421+39D,20200412+39D', comments => '39 Days after Easter');
    DBMS_SCHEDULER.CREATE_SCHEDULE('Pentecost_Monday', repeat_interval => 'FREQ=YEARLY;BYDATE=20150405+50D,20160327+50D,20170416+50D,20170416+50D,20180401+50D,20190421+50D,20200412+50D', comments => '50 Days after easter');

    DBMS_SCHEDULER.CREATE_SCHEDULE('Repentance_and_Prayer', repeat_interval => 'FREQ=DAILY;BYDATE=1122-SPAN:7D;BYDAY=WED', 
        comments => 'Wednesday before November 23th, Buss- und Bettag');
    -- alternative solution: 
    --DBMS_SCHEDULER.CREATE_SCHEDULE('Repentance_and_Prayer', repeat_interval => 'FREQ=MONTHLY;BYMONTH=NOV;BYDAY=3 WED', 
    --    comments => '3rd Wednesday in November');

    DBMS_SCHEDULER.CREATE_SCHEDULE('Labor_Day', repeat_interval => 'FREQ=YEARLY;BYDATE=0501');
    DBMS_SCHEDULER.CREATE_SCHEDULE('German_Unity_Day', repeat_interval => 'FREQ=YEARLY;BYDATE=1003');
    DBMS_SCHEDULER.CREATE_SCHEDULE('Christmas', repeat_interval => 'FREQ=YEARLY;BYDATE=1225+SPAN:2D');

    DBMS_SCHEDULER.CREATE_SCHEDULE('Christian_Celebration_Days', repeat_interval => 'FREQ=DAILY;INTERSECT=Easter_Sunday,Good_Friday,Easter_Monday,Ascension_Day,Pentecost_Monday,Repentance_and_Prayer,Christmas');
    -- alternative solution: 
    -- DBMS_SCHEDULER.CREATE_SCHEDULE('Christian_Celebration_Days', repeat_interval => 'FREQ=Good_Friday;BYDAY=1 MON, 6 THU,8 MON');
    DBMS_SCHEDULER.CREATE_SCHEDULE('Political_Holidays', repeat_interval => 'FREQ=DAILY;INTERSECT=New_Year,Labor_Day,German_Unity_Day');

END;
/

请参阅此处的日历语法:Calendaring Syntax

然后创建一个类似于此的函数:

CREATE OR REPLACE FUNCTION GetBusinessHours(start_time IN TIMESTAMP, end_time IN TIMESTAMP) RETURN INTERVAL DAY TO SECOND AS
    next_run_date TIMESTAMP := start_time;
     duration INTERVAL DAY(3) TO SECOND(0) := INTERVAL '0' HOUR;
BEGIN
    LOOP
        DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=HOURLY;INTERVAL=1;BYHOUR=8,9,10,11,13,14,15,16,17;BYDAY=MON,TUE,WED,THU,FRI,SAT; EXCLUDE=NEW_YEARS_DAY,MARTIN_LUTHER_KING_DAY,WASHINGTONS_BIRTHDAY,MEMORIAL_DAY,INDEPENDENCE_DAY,CHRISTMAS_DAY,SPRING_BREAK', NULL, next_run_date, next_run_date);
        duration := duration + INTERVAL '1' HOUR;
        EXIT WHEN next_run_date >= end_time;
    END LOOP;
    RETURN duration;
END;

CREATE OR REPLACE FUNCTION GetBusinessStart(start_time IN TIMESTAMP, end_time IN TIMESTAMP) RETURN TIMESTAMP AS
    next_run_date TIMESTAMP := start_time;
BEGIN
    DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=HOURLY;INTERVAL=1;BYHOUR=8,9,10,11,13,14,15,16,17;BYDAY=MON,TUE,WED,THU,FRI,SAT; EXCLUDE=NEW_YEARS_DAY,MARTIN_LUTHER_KING_DAY,WASHINGTONS_BIRTHDAY,MEMORIAL_DAY,INDEPENDENCE_DAY,CHRISTMAS_DAY,SPRING_BREAK', NULL, next_run_date, next_run_date);
    RETURN next_run_date;
END;


CREATE OR REPLACE FUNCTION GetBusinessEnd(start_time IN TIMESTAMP, end_time IN TIMESTAMP) RETURN TIMESTAMP AS
    next_run_date TIMESTAMP := start_time;
BEGIN
    LOOP
        DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=HOURLY;INTERVAL=1;BYHOUR=8,9,10,11,13,14,15,16,17;BYDAY=MON,TUE,WED,THU,FRI,SAT; EXCLUDE=NEW_YEARS_DAY,MARTIN_LUTHER_KING_DAY,WASHINGTONS_BIRTHDAY,MEMORIAL_DAY,INDEPENDENCE_DAY,CHRISTMAS_DAY,SPRING_BREAK', NULL, next_run_date, next_run_date);
        EXIT WHEN next_run_date >= end_time;
    END LOOP;
    RETURN next_run_date;
END;

如果您不必考虑公共假期,请跳过EXCLUDE=...部分。

然后您可以在查询中使用该功能:

SELECT TASK, 
   GetBusinessStart(START_TIME, END_TIME),
   GetBusinessEnd(START_TIME, END_TIME),
   GetBusinessHours(START_TIME, END_TIME)
FROM ...;

注意,如果START_TIME和END_TIME都落入同一个非工作日,该功能需要进行一些微调。