Функция number_of_days (start_date, end_date) должна вычислять количество рабочих дней между start_date и end_date. Он работает для любой start_date, если это не воскресенье .
Расчет состоит из 3 шагов:
взять количество дней до начала целой недели (целая неделя может быть 0 для дат с небольшим диапазоном, таких как 21–24)
затем добавляются целые недели
берет все дни, оставшиеся после целой недели, и складывает их.
Мне нужен 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;
2 ответа
Вы просто слишком усложняете это ... Во-первых, лучше работать в неделях ISO: это начинается с понедельника.
- https://en.wikipedia.org/wiki/ISO_week_date
- https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm
- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/TRUNC-date.html#GUID-BC82227A-2698-4EC8-8C1A-ABECC64B0E79
Таким образом, вы можете легко получить первый день недели 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;
/
Это должно помочь вам начать работу, поскольку я не понимаю ваших требований.
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
Похожие вопросы
Новые вопросы
sql
Язык структурированных запросов (SQL) - это язык запросов к базам данных. Вопросы должны включать примеры кода, структуру таблицы, примеры данных и тег для используемой реализации СУБД (например, MySQL, PostgreSQL, Oracle, MS SQL Server, IBM DB2 и т. Д.). Если ваш вопрос относится исключительно к конкретной СУБД (использует определенные расширения / функции), используйте вместо этого тег этой СУБД. Ответы на вопросы, помеченные SQL, должны использовать стандарт ISO / IEC SQL.
javascript
?