How to eliminate non-working hours in Oracle

前端 未结 2 439
攒了一身酷
攒了一身酷 2020-12-18 16:34

I have two columns of DateTime type. The first one stores the DateTime when a process it started, the other one stores the DateTime when that process is finished. I want to

2条回答
  •  爱一瞬间的悲伤
    2020-12-18 17:15

    If i understand correctly, you want to calculate the difference between the start and finish date excluding the time before 10 am and after 7 pm.

    Here's the sample query and sql fiddle.

    SELECT start_time,
           finish_time,
           interval_time,
           EXTRACT (HOUR FROM interval_time), --extract the hours,mins and seconds from the interval
           EXTRACT (MINUTE FROM interval_time),
           EXTRACT (SECOND FROM interval_time)
      FROM (SELECT start_time,
                   finish_time,
                   NUMTODSINTERVAL (
                        CASE
                           WHEN finish_time - TRUNC (finish_time) > (19 / 24) --if finish time is after 7pm
                           THEN
                              TRUNC (finish_time) + (19 / 24)      --set it to 7pm
                           ELSE
                              finish_time      --else set it to actual finish time
                        END
                      - CASE
                           WHEN start_time - TRUNC (start_time) < (10 / 24) --if start time is before 10 am
                           THEN
                              TRUNC (start_time) + (10 / 24)    --set it to 10 am.
                           ELSE
                              start_time    --else set it to the actual start time
                        END,
                      'day') --subtract the both and convert the resulting day to interval
                      interval_time
              FROM timings);
    

    What I have done is,

    • Check if the start time is before 10 am and finish time is after 7 pm. If so, set the time to 10 am and 7 pm.
    • Then subtract the dates and convert the resulting days to Interval Type.
    • Then extract the hours, mins and seconds from the Interval.

    Note: This query assumes that both dates fall on same day and both are not before 10am or after 7 pm.

    UPDATE: To exclude holidays, the query will become complicated. I suggest writing three functions and use these functions in the query.

    1st function:

    FUNCTION modify_start_time (p_in_dte DATE) RETURN DATE
    ----------------------------------
    IF p_in_dte - TRUNC (p_in_dte) < (10 / 24)
    THEN
       RETURN TRUNC (p_in_dte) + (10 / 24);
    ELSIF p_in_dte - TRUNC (p_in_dte) > (19 / 24)
    THEN
       RETURN TRUNC (p_in_dte) + 1 + (10 / 24);
    ELSE
       RETURN p_in_dte;
    END IF;
    

    If the start time is outside the work hours, modify the start time to next nearest start time.

    2nd function:

    FUNCTION modify_finish_time (p_in_dte DATE) RETURN DATE
    ----------------------------------
    IF p_in_dte - TRUNC (p_in_dte) > (19 / 24)
    THEN
       RETURN TRUNC (p_in_dte) + (19 / 24);
    ELSIF p_in_dte - TRUNC (p_in_dte) < (10 / 24)
    THEN
       RETURN TRUNC (p_in_dte) - 1 + (19 / 24);
    ELSE
       RETURN p_in_dte;
    END IF;
    

    If the finish time is outside the work hours, modify it to the previous nearest finish time.

    3rd function:

    FUNCTION get_days_to_exclude (p_in_start_date     DATE,
                                  p_in_finish_date    DATE) RETURN NUMBER
    --------------------------------------------------------
    WITH cte --get all days between start and finish date
         AS (    SELECT p_in_start_date + LEVEL - 1 dte
                   FROM DUAL
             CONNECT BY LEVEL <= p_in_finish_date + 1 - p_in_starT_date)
    SELECT COUNT (1) * 9 / 24    --mutiply the days with work hours in a day
      INTO l_num_holidays
      FROM cte
     WHERE    TO_CHAR (dte, 'dy') = 'sun'    --find the count of sundays
           OR dte IN     --fins the count of holidays, assuming leaves are stored in separate table
                 (SELECT leave_date  
                    FROM leaves
                   WHERE leave_date BETWEEN p_in_start_date
                                        AND p_in_finish_date);
    
    l_num_holidays :=
       l_num_holidays + ( (p_in_finish_date - p_in_start_date) * (15 / 24)); --also, if the dates span more than a day find the non working hours.
    
    RETURN l_num_holidays;
    

    This function finds the no of days to be excluded while calculating the duration.

    So, the final query should be something like this,

    SELECT start_time,
           finish_time,
           CASE
              WHEN work_duration < 0 THEN NUMTODSINTERVAL (0, 'day')
              ELSE NUMTODSINTERVAL (work_duration, 'day')
           END
      FROM (SELECT start_time, finish_time,
                   --modify_start_time (start_time), modify_finish_time (finish_time),
                     modify_finish_time (finish_time)
                   - modify_start_time (start_time)
                   - get_days_to_exclude (
                        TRUNC (modify_start_time (start_time)),
                        TRUNC (modify_finish_time (finish_time)))
                      work_duration
              FROM timings);
    

    If the duration is less than 0, ignore it by setting it to 0.

提交回复
热议问题