Начиная с вашего примера с 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