SQL date diff без учета ночей - PullRequest
       15

SQL date diff без учета ночей

2 голосов
/ 01 ноября 2011

используя базу данных Oracle, мне нужно знать разницу между любыми двумя датами (в секундах), не учитывая интервал с 6 вечера до 8 утра.

Так, например, разница между 01.11.2011 13:00 и 03.11.2011 13:00 составляет 20 часов (или 72000 секунд соответственно). Мне также нужно игнорировать выходные и праздничные дни, но я уже получил для этого функцию PLSQL.

Заранее спасибо!

С уважением, Alex


EDIT
Тем временем я сам придумал решение, которое, честно говоря, не очень сложное, но выполняет работу:

CREATE OR REPLACE FUNCTION "GET_WORKTIME" 
(
  startdate_in in date,
  enddate_in   in date
)
RETURN NUMBER
AS
  days_between number(4);
  duration number;
  end_of_first_day date;
  start_of_last_day date;
  startdate date;
  enddate date;
  weekday number(1);
  temp_date date;
  holidays day_array;
  is_holiday boolean;
BEGIN
  duration     := 0;
  startdate    := startdate_in;
  enddate      := enddate_in;

IF (startdate IS NULL OR enddate IS NULL OR startdate >= enddate) THEN
  RETURN 0;
END IF;

days_between := trunc(enddate) - trunc(startdate);

end_of_first_day  := to_date(concat(to_char(startdate, 'dd.mm.yyyy'), ' 18:00:00'), 'dd.mm.yyyy hh24:mi:ss');
start_of_last_day := to_date(concat(to_char(enddate,   'dd.mm.yyyy'), ' 08:00:00'), 'dd.mm.yyyy hh24:mi:ss');

temp_date := startdate;

FOR i IN 0..days_between LOOP
    -- if it is the first day, just calculate the time until the end of that day
    IF i = 0 THEN
        duration := duration + greatest((end_of_first_day - startdate), 0) * 24 * 3600;

    -- if it is the last day, just calculate the time until that point in time
    ELSIF i = days_between THEN
        duration := duration + greatest((enddate - start_of_last_day),  0) * 24 * 3600;            

    -- for every other day, simply add 10 hours (6pm - 8am) if it is neither
    -- saturday or sunday nor a holiday
    ELSE
        weekday    := to_number(to_char(temp_date, 'D'));
        is_holiday := false;

        -- weekend?
        IF (NOT weekday IN (6,7)) THEN

            -- holiday?
            FOR j IN extract(year FROM startdate)..extract(year FROM enddate) LOOP
                holidays := get_holidays_for_year(j);

                FOR k IN 1..holidays.count LOOP
                    IF (holidays(k) = trunc(temp_date)) THEN
                        is_holiday := true;
                    END IF;
                END LOOP;
            END LOOP;

            -- add 10 hours to the duration
            IF (NOT is_holiday) THEN
                duration := duration + 10 * 3600;
            END IF;
        END IF;
    END IF;

    temp_date := temp_date + 1;
END LOOP;
RETURN duration;
END;

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

С уважением, Alex

Ответы [ 2 ]

2 голосов
/ 01 ноября 2011

Проще всего разбить это на 3 части:

  1. Первый неполный день (если есть)
  2. Последний неполный день (если есть)
  3. Промежуточные целые дни (если есть)

Первый неполный день - от позднего (время начала, 8:00) до раннего (last_date, 18:00 в первый день), поэтому мы вычитаемте, чтобы получить время для первого дня:

greatest ( least ( trunc(first_date)+18/24
                        , last_date
                        ) - greatest( first_date
                                    , trunc(first_date)+8/24
                                    )
                , 0) * 24 as last_day_hours

Внешняя функция greatest гарантирует, что мы не получим отрицательное значение, например, если время начала после 6 часов вечера. * 10101

Еслипоследний день не совпадает с первым днем, тогда последний неполный день - с 8:00 до (конечное время) последнего дня:

   case when trunc(last_date) != trunc(first_date) 
        then greatest ( least( last_date
                             , trunc(last_date)+18/24
                             )
                        - (trunc(last_date)+8/24)
                      , 0) * 24
        else 0 end as last_day_hours

Все промежуточные дни (в часах):

   greatest ( (trunc(last_date) - (trunc(first_date)+1))
            , 0
            ) * 10 as intervening_days_in_hours

10 - количество часов с 8:00 до 18:00

Таким образом, сложение этих трех значений вместе дает общее количество часов за период, а умножение общей суммы на 3600 дает секунды:

(
   (greatest ( least ( trunc(first_date)+18/24
                    , last_date
                    ) - greatest( first_date
                                , trunc(first_date)+8/24
                                )
            , 0) * 24)
 + case when trunc(last_date) != trunc(first_date) 
        then greatest ( least( last_date
                             , trunc(last_date)+18/24
                             )
                        - (trunc(last_date)+8/24)
                      , 0) * 24
        else 0 end 
 + (greatest ( (trunc(last_date) - (trunc(first_date)+1))
            , 0
            ) * 10)
) * 3600 as total_seconds

Я не могу не чувствовать, что это должно быть легче, чем это, хотя!И, конечно, это не учитывает выходные.

0 голосов
/ 01 ноября 2011

В Oracle вы можете просто вычесть две даты. Вы можете рассчитать количество секунд, разделив результат на (24 * 60 * 60)

select (last_date - first_date) / (24*60*60) as seconds from table

Хитрость для форматирования разницы заключается в следующем:

select to_char(trunc(sysdate) + (last_date - first_date), 'hh24:mi:ss') as time_between from table
...