Рассчитать рабочие часы между двумя датами без создания функции или представления - PullRequest
1 голос
/ 03 июня 2019

Я понимаю, что это может быть несколько излишним вопросом, НО я изо всех сил пытался следовать некоторым из найденных примеров, и я подумал, что я спрошу еще раз, предоставив детали моего конкретного сценария.

Вот почему я работаю с:

  • База данных Oracle
  • Даты в формате отметки времени
  • Я не могу создать дополнительные таблицы / представления (из-за проблем с разрешениями)
  • Я не могу создавать какие-либо пользовательские функции (из-за проблем с разрешениями)
  • У меня есть 40-часовая рабочая неделя и рабочие часы с 8 до 4:30 с понедельника по пятницу. (Я полагаю, что технически у нас остается более 40 часов для учета б / у. Я не хочу, чтобы НАСТОЯЩАЯ ВЫГОДНО беспокоилась об исключении перерывов на обед) *

Я могу вычислить часы, но не знаю, как получить компонент рабочего дня.

1 Ответ

2 голосов
/ 03 июня 2019

Начиная с вашего примера с 8:00 с пятницы по 9:00 понедельника:

with dates as (
 Select timestamp '2019-05-31 08:00:00' start_date
      , timestamp '2019-06-03 09:00:00' end_date
   from dual
)

Нам нужно создать промежуточные дни. Мы можем сделать это с помощью рекурсивного запроса:

, recur(start_date, calc_date, end_date) as (
  -- Anchor Part
  select start_date
       , trunc(start_date)
       , end_date
    from dates

  -- Recrusive Part
  union all
  select start_date
       , calc_date+1
       , end_date
    from recur
   where calc_date+1 < end_Date
)

Исходя из этого, нам нужно выяснить несколько вещей, например: есть ли calc_day день недели или выходные, и каковы начальное и конечное время для calc_day, мы можем затем взять эти значения и использовать небольшую арифметику даты, чтобы найти количество часов, отработанных в этот день (возвращается как интервал дня к секунде, так как мы начали с отметок времени):

, days as (
  select calc_date
       , case when mod(to_number(to_char(calc_date,'d'))-1,6) != 0 then 1 end isWeekDay
       , greatest(start_date, calc_date + interval '8' hour) start_time
       , least(end_date, calc_date      + interval '16:30' hour to minute) end_time

       , least( ( least(end_date, calc_date      + interval '16:30' hour to minute) 
                - greatest(start_date, calc_date + interval '8' hour)
                ) * case when mod(to_number(to_char(calc_date,'d'))-1,6) != 0 then 1 end
              , interval '8' hour
         ) daily_hrs
    from recur
   where start_date < (calc_date + interval '16:30' hour to minute)
     and (calc_date + interval '8' hour) < end_date
)

Обратите внимание, что на предыдущем шаге мы ограничили ежедневные часы 8 часами в день, а пункт where защищает дни начала или окончания, которые не являются рабочими часами. Последний шаг заключается в суммировании часов. К сожалению, у Oracle нет собственных агрегатных или аналитических функций интервалов, но мы все же можем управлять, конвертируя интервалы в секунды, суммируя их, а затем преобразовывая их обратно в интервал для вывода:

select calc_date
     , daily_hrs
     , numtodsinterval(sum( extract(hour   from daily_hrs)*60*60
                          + extract(minute from daily_hrs)*60
                          + extract(second from daily_hrs)
                          ) over (order by calc_date)
                      ,'second') run_sum
  from days;

Я сделал приведенную выше сумму в качестве аналитической функции, чтобы мы могли видеть некоторые промежуточные данные, но если вы просто хотите получить конечный результат, вы можете изменить последнюю часть запроса следующим образом:

select numtodsinterval(sum( extract(hour from daily_hrs)*60*60
                          + extract(minute from daily_hrs)*60
                          + extract(second from daily_hrs)
                          )
                      ,'second') run_sum

Вот дБ <> скрипка со всем запросом в действии. Обратите внимание, что в скрипте я изменил настройку NLS_TERRITORY сеанса БД на AMERICA, чтобы запрос работал, так как первый день недели зависит от страны. Второй запрос в скрипте заменяет специфическую для территории функцию:

case when mod(to_number(to_char(calc_date,'d'))-1,6) != 0 then 1 end

с расчетом местоположения и языковой независимости:

case when (mod(mod(calc_date - next_day(date '2019-1-1',to_char(date '2019-01-06','day')),7),6)) != 0 then 1 end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...