Функция number_of_days (start_date, end_date) должна вычислять количество рабочих дней между start_date и end_date. Он работает для любой start_date, если это не воскресенье .

Расчет состоит из 3 шагов:

  1. взять количество дней до начала целой недели (целая неделя может быть 0 для дат с небольшим диапазоном, таких как 21–24)

  2. затем добавляются целые недели

  3. берет все дни, оставшиеся после целой недели, и складывает их.

Мне нужен 2-й шаг, чтобы получить целое число, например 0, 5, 10, вместо этого я получаю 0,7142857142857142857142857142857142857 в этом примере, потому что параметр start_date - воскресенье.

Я мог бы использовать ROUND (), чтобы решить эту проблему, но, может быть, есть способ лучше?

    CREATE OR REPLACE FUNCTION number_of_days(start_date IN DATE, end_date IN DATE)
    RETURN  NUMBER
    
    IS  v_number_of_days NUMBER;
        first_week_day DATE := TO_DATE('31-12-2017', 'DD-MM-YYYY');
    
    BEGIN
--step 1
    SELECT  ( CASE  WHEN MOD(start_date - first_week_day, 7) BETWEEN 2 AND 5
                    THEN 6 - MOD(start_date - first_week_day, 7)
                    ELSE 0  END )
            +
--step 2
            ((  CASE  WHEN MOD(end_date - first_week_day, 7) < 7
                      THEN end_date - MOD(end_date - first_week_day, 7)
                      ELSE end_date  END )
              -
              ( CASE  WHEN MOD(start_date - first_week_day, 7) > 1
                      THEN start_date + 8 - MOD(start_date - first_week_day, 7)
                      ELSE start_date  END ) + 1
            ) / 7 * 5
            +
--step 3
            ( CASE  WHEN MOD(end_date - first_week_day,7) BETWEEN 1 AND 6
                    THEN CASE   WHEN MOD(end_date - first_week_day, 7) = 6
                                THEN MOD(end_date - first_week_day, 7) - 1
                                ELSE MOD(end_date - first_week_day, 7) END
                    ELSE 0 END  )
    INTO    v_number_of_days
    FROM    DUAL;
    
    RETURN  v_number_of_days;
    
    END;
    

    --test
    SELECT  number_of_days(TO_DATE('21-11-2021', 'DD-MM-YYYY'), TO_DATE('24-11-2021', 'DD-MM-YYYY'))
    FROM    DUAL;
0
adict 21 Ноя 2021 в 23:51
1
Какую роль во всем этом играет javascript?
 – 
ASDFGerte
21 Ноя 2021 в 23:53

2 ответа

Лучший ответ

Вы просто слишком усложняете это ... Во-первых, лучше работать в неделях ISO: это начинается с понедельника.

Таким образом, вы можете легко получить первый день недели ISO, используя trunc(dt, 'IW'). Например, 19 ноября - пятница, и мы можем легко получить первый день этой недели, используя trunc(date'2021-11-19','IW'):

SQL> select trunc(date'2021-11-19','IW') xx from dual;

XX
-------------------
2021-11-15 00:00:00

Итак, 2021-11-15 - понедельник.

А теперь представим, что у нас есть даты:

 Mo Tu We Th Fr Sa Su
 -- -- -- -- -- -- --
           X  X  X  X
  X  X  X  X  X  X  X
  X  X  X  X  X  X  X
  X  X  X  X  X  X  X
  X  X  X  X  X  X  X
  X  X  X  X  X  X  X
  X  X  X  X  X  X  X
  X  X

Мы можем легко получить количество рабочих дней в полных неделях (count_full_weeks * 5/7), поэтому давайте исключим их:

 Mo Tu We Th Fr Sa Su
 -- -- -- -- -- -- --
           X  X  X  X
  X  X

Затем давайте преобразуем его графически, поскольку мы знаем, что количество оставшихся дней всегда будет меньше 7 дней (меньше недели), а первый день будет одним из первых 7 дней:

 Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
 -- -- -- -- -- -- -- -- -- -- -- -- -- --
           X  X  X  X  X  X

Заменим Mo, Tu, We ... их числами:

  0  1  2  3  4  5  6  0  1  2  3  4  5  6
 -- -- -- -- -- -- -- -- -- -- -- -- -- --
           X  X  X  X  X  X

Поскольку мы знаем, что количество дней в этой неполной неделе не может быть 7, мы знаем, что последний конец будет следующим:

  0  1  2  3  4  5  6  0  1  2  3  4  5  6
 -- -- -- -- -- -- -- -- -- -- -- -- -- --
                    X  X  X  X  X  X

А теперь заменим цифры второй недели на 7-13:

  0  1  2  3  4  5  6  7  8  9 10 11 12 13
 -- -- -- -- -- -- -- -- -- -- -- -- -- --
                    X  X  X  X  X  X

Теперь мы понимаем, что нам нужно проверять только 5-й и 6-й дни, то есть проверять, есть ли в нашем периоде 5 и 6.

Так будет так (я подробно описал):

CREATE OR REPLACE FUNCTION number_of_days(start_date IN DATE, end_date IN DATE)
    RETURN  NUMBER
as
    days_between        int;
    full_weeks          int;
    remaining_days      int;
    remaining_workdays  int;
    
    v_first_week_day    date;
    v_start             int;
    v_end               int;
    
    result              int;
begin
    days_between := end_date - start_date;
    full_weeks := trunc(days_between/7);
    remaining_days:= mod(days_between,7); -- or days_between-full_weeks*7
    
    v_first_week_day := trunc(start_date,'IW');
    v_start := start_date - v_first_week_day;
    v_end := v_start + remaining_days - 1;
    
    remaining_workdays := remaining_days
                        - case when 5 between v_start and v_end then 1 else 0 end
                        - case when 6 between v_start and v_end then 1 else 0 end
                        ;
    result := full_weeks * 5 + remaining_workdays;
    RETURN result;
end;
/

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

CREATE OR REPLACE FUNCTION number_of_days(start_date IN DATE, end_date IN DATE)
    RETURN  NUMBER
as
begin
    return 5*trunc((end_date-start_date)/7) + mod((end_date-start_date),7) 
     - case when 5-(start_date - trunc(start_date,'IW')) between 0 and mod((end_date-start_date),7)-1 then 1 else 0 end
     - case when 6-(start_date - trunc(start_date,'IW')) between 0 and mod((end_date-start_date),7)-1 then 1 else 0 end
    ;
end;
/
3
Sayan Malakshinov 22 Ноя 2021 в 04:14

Это должно помочь вам начать работу, поскольку я не понимаю ваших требований.

with time_between as (
  select trunc ( timestamp'2021-06-14 06:00:00' ) - 
           trunc ( timestamp'2021-06-08 14:00:00' ) t
  from   dual
)
  select *
  from   time_between;

T   
   6
0
Beefstu 22 Ноя 2021 в 00:04