powered by simpleCommunicator - 2.0.49     © 2025 Programmizd 02
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Количество месяцев между датами
22 сообщений из 22, страница 1 из 1
Количество месяцев между датами
    #40101391
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Подскажите, как доработать формулу расчета.

У меня есть расчетный период, который определяется тремя параметрами:
- тип периода: D (сутки), W (неделя), M (месяц), Y (год)
- величина (натуральное число)
- значение отсчета (отсутствует или натуральное число)

Формулу расчетного периода можно записать например так:
3M - расчетным периодом является 3 календарных месяца, начиная с текущей даты
1M1 - расчетным периодом является один месяц, расчетный период всегда начинается с 1 числа месяца

У меня есть две даты, известен расчетный период и мне нужно посчитать, сколько расчетных периодов размещается между этими двумя датами.
Я это считаю таким выражением:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
, case TF.UNIT
    when 'D' then (trunc(S.RECKONING_DATE) - trunc(ST.DATE_BEG)) / TF.QTY
    when 'W' then trunc((trunc(S.RECKONING_DATE) - ST.DATE_BEG - decode(TF.ZERO, 0, 0, TF.QTY - 1 + (trunc(ST.DATE_BEG, 'DD') - trunc(ST.DATE_BEG, 'IW'))) + 1) / TF.QTY / 7)
    when 'M' then trunc(months_between(trunc(S.RECKONING_DATE), to_date(to_char(ST.DATE_BEG,'YYYY')||to_char(ST.DATE_BEG,'MM')||to_char(decode(TF.ZERO, 0, extract(day from ST.DATE_BEG), TF.ZERO), 'FM00'), 'YYYYMMDD')) / TF.QTY)
    when 'Y' then trunc(months_between(trunc(S.RECKONING_DATE), to_date(to_char(ST.DATE_BEG,'YYYY')||to_char(decode(TF.ZERO, 0, extract(month from ST.DATE_BEG), TF.ZERO), 'FM00')||to_char(decode(TF.ZERO, 0, extract(day from ST.DATE_BEG), 1), 'FM00'), 'YYYYMMDD'))  / TF.QTY / 12)
    else null
  end                                  as PERIODS


Интервал задается датами ST.DATE_BEG и S.RECKONING_DATE, расчетный период задается TF.UNIT, TF.QTY и TF.ZERO.

Но есть проблема с месячными периодами, когда длина месяцев разная.
Например: select months_between(date'2022-02-28', date'2021-08-30') from dual - возвращает 5.94
А нужно получить 6.

Не посоветуете, как это сделать?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101392
Фотография andrey_anonymous
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Может и посоветуем, если скажете, сколько должно получиться между датами:
date'2022-02-28', date'2021-08-31'
date'2022-02-28', date'2021-08-30'
date'2022-02-28', date'2021-08-28'

date'2022-02-27', date'2021-08-31'
date'2022-02-27', date'2021-08-30'

date'2022-02-20', date'2021-08-20'
date'2022-02-15', date'2021-08-17'
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101416
Фотография Vadim Lejnin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.,
Смотря что Вам надо...

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
with t (d1,d2) as (
select date '2022-02-28', date '2021-08-31' from dual union all
select date '2022-02-28', date '2021-08-30' from dual union all
select date '2022-02-28', date '2021-08-28' from dual union all
select date '2022-02-27', date '2021-08-31' from dual union all
select date '2022-02-27', date '2021-08-30' from dual union all
select date '2022-02-20', date '2021-08-20' from dual union all
select date '2022-02-15', date '2021-08-17' from dual
)
select months_between(d1,d2) as p1,round(months_between(d1,d2)) as p2 from t
/
        P1         P2
---------- ----------
         6          6
5.93548387          6
         6          6
5.87096774          6
5.90322581          6
         6          6
5.93548387          6

7 rows selected.
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101421
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
С этими датами работает ИС на Perl и насколько я изучил исходники, алгоритм расчета следующий:

Для дат с 1 по 28 считается число календарных месяцев. С 1 по 2 число месяцев будет больше единицы, со 2 по 1 число месяцев будет меньше единицы. Другими словами, идентично months_between.

Если в конечном месяце количество дней меньше, чем число начальной даты, то результатом будет целое число (месяцев). Можно сказать, что в этом случае для обоих дат используется число конечного месяца. То есть с 31 января до 28 февраля ровно месяц, как и с 28 января до 28 февраля.

Если в начальном месяце количество дней меньше, чем число конечной даты, то опять таки, считается как months_between. С 28 февраля до 29 марта будет больше единицы (если только год не високосный).

Выражение у меня и так громоздкое, и если в него добавить case и least, то получится вообще нечто нечитаемое.
Но может быть есть какой-то хитрый и изящный способ?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101448
Фотография andrey_anonymous
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
with t (dt_till,dt_from) as (
select date '2022-02-28', date '2022-01-27' from dual union all
select date '2022-02-28', date '2022-01-28' from dual union all
select date '2022-02-28', date '2022-01-31' from dual union all
select date '2022-02-28', date '2021-08-31' from dual union all
select date '2022-02-28', date '2021-08-30' from dual union all
select date '2022-02-28', date '2021-08-28' from dual union all
select date '2022-02-27', date '2021-08-31' from dual union all
select date '2022-02-27', date '2021-08-30' from dual union all
select date '2022-02-20', date '2021-08-20' from dual union all
select date '2022-02-15', date '2021-08-17' from dual
)
select dt_from, dt_till
     , months_between( dt_till
                     , case when -- Если в конечном месяце количество дней меньше, чем число начальной даты
                                 -- этом случае для обоих дат используется число конечного месяца
                         to_char(dt_from, 'DD') > to_char(last_day(dt_till), 'DD')
                             then trunc(dt_from,'mm') + extract(day from last_day(dt_till))-1
                             else -- иначе как months_between пошлёт
                                 dt_from
                       end
                     ) as PERIODS_M
 from t
;

DT_FROM     DT_TILL      PERIODS_M
----------- ----------- ----------
2022-01-27  2022-02-28  1.03225806
2022-01-28  2022-02-28           1
2022-01-31  2022-02-28           1
2021-08-31  2022-02-28           6
2021-08-30  2022-02-28           6
2021-08-28  2022-02-28           6
2021-08-31  2022-02-27  5.96774193
2021-08-30  2022-02-27  5.96774193
2021-08-20  2022-02-20           6
2021-08-17  2022-02-15  5.93548387

10 rows selected


SQL> 
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101452
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Да, примерно так, только в моем случае вместо dt_from будет здоровенное выражение (ну либо нужно оборачивать в подзапрос).
Спасибо за готовый пример, попробую его подставить в свой запрос.
Я думал, что может быть получится выражение переписать так, чтобы оно было более компактным. Например вместо case when ... < ... просто вычитать из первой даты определенное значение. Но не смог придумать, как именно это сделать.
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101555
SQL*Plus
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.,

Используйте MONTHS_BETWEEN, как показано выше.
Или договоритесь с постановщиком задачи, что речь идет не про "месяцы", а про "тридцатидневки", например.
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101684
Фотография Stax
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.
Да, примерно так,

интервал в месяцах, понятие размытое, хотя часто применяется,

через 6 месяцев, не раньше чем за месяц, депозит на пол года, тощо

напр
c 01 по 28 февраля = ровно 1 месяц у невысокосном году,

начало 29 января, когда набежит 1 месяц? "депозит на месяц", когда за процкнтами приходить?

....
stax
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101685
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Понятие месяца можно обозначить так: берется последний день месяца для конечной даты, если в начальной дате тоже последний день месяца, либо номер дня больше количества дней в конечном месяце.
Согласен, что несколько искусственно, но может быть такая трактовка месяца уже есть в какой-нибудь финансовой функции из дополнительных пакетов.
Но раз нет, то нет. Наверное есть смысл самому написать такую функцию в виде package в теле PL/SQL кода (не добавляя в БД).

Не подскажите, как следует делать эту функцию, чтобы она была эффективной при использовании в SQL?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101692
Фотография Stax
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.

Наверное есть смысл самому написать такую функцию в виде package в теле PL/SQL кода (не добавляя в БД).



что имеется в ввиду под "не добавляя в БД"?

.....
stax
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101696
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Попробовал сделать так:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
DECLARE

FUNCTION PERIOD_LEN(date1 DATE, date2 DATE, period VARCHAR2 := 'M', len NUMBER := 1) RETURN NUMBER
IS
  d1 DATE;
  d2 DATE;
  l1 NUMBER;
  l2 NUMBER;
BEGIN
  d1 := trunc(least(date1, date2));
  d2 := trunc(greatest(date1, date2));
  IF (d1 = d2) THEN RETURN 0; END IF;
  l1 := extract(day from last_day(d1));
  l2 := extract(day from last_day(d2));
  RETURN months_between(d1, d2);
END;

BEGIN
  open :RECORDSET for
  with t as
  (
    select sysdate d1, sysdate d2 from dual where 0=1
    union all select date '2022-02-28', date '2022-01-27' from dual
    union all select date '2022-02-28', date '2022-01-28' from dual
    union all select date '2022-02-28', date '2022-01-31' from dual
    union all select date '2022-02-28', date '2021-08-31' from dual
    union all select date '2022-02-28', date '2021-08-30' from dual
    union all select date '2022-02-28', date '2021-08-28' from dual
    union all select date '2022-02-27', date '2021-08-31' from dual
    union all select date '2022-02-27', date '2021-08-30' from dual
    union all select date '2022-02-20', date '2021-08-20' from dual
    union all select date '2022-02-15', date '2021-08-17' from dual
  )
  select d1, d2, period_len(d1, d2)
  from t;
END;



Но получаю ошибку ORA-06550 (функция не может быть использована в SQL).
Это из-за декларативных операторов (IF)? Или функции в анонимном блоке использовать нельзя?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101697
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Stax
что имеется в ввиду под "не добавляя в БД"?

Не создавая объекты в БД.
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101704
Фотография Stax
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.,

так нельзя
SQL не видит Вашу ф-цию

у Вас 10-ка, и "Не создавая объекты в БД", печалька

.....
stax
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101707
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Понятно, спасибо.

Тогда буду делать package.
Есть ли смысл избегать в этой функции (которая будет использоваться в SQL) декларативных подходов и обходиться сложным case?
Или никакой выгоды для производительности/эффективности это не даст?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101709
Фотография Stax
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.,

"сложный case" сопровождать тяжело

зы
создайте две ф-ции и сравните сколько мкс Вы выиграете

....
stax
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101754
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Подскажите, чем может быть вызван такой подход разработчика?
В базе данных ИС есть несколько вспомогательных функций, которые написаны примерно так:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
   FUNCTION period_days (
      cnt_period_id_arg   IN   bm_cnt_period.cnt_period_id%TYPE,
      date_arg            IN   DATE DEFAULT TRUNC (SYSDATE, 'DD')
   )
      RETURN NUMBER
   IS
      date_var     DATE;
      period_var   bm_cnt_period%ROWTYPE;
   BEGIN
      SELECT *
        INTO period_var
        FROM bm_cnt_period
       WHERE bm_cnt_period.cnt_period_id = cnt_period_id_arg;

      date_var := TRUNC (date_arg, 'DD');
...


Хочу в этот же package добавить функцию period_count, которая будет подсчитывать нужное мне значение между двумя датами.

В функцию передается идентификатор, по которому из таблицы считываются основные характеристики периода (тип, количество, отсчет).
Почему сделано именно так? Я бы скорее характеристики периода передал бы в виде трех аргументов (чтобы в теле функции не делать отдельного запроса).
Или есть какие-то причины, чтобы делать именно так?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101774
Фотография Stax
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.,

так решил архитектор

если характеристики "идентификатора" поменяются, то код не приденся менять

.....
stax
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101807
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сделал так:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
  FUNCTION period_count (
             p_date_from IN DATE DEFAULT TRUNC(SYSDATE, 'DD') ,
             p_date_next IN DATE DEFAULT TRUNC(SYSDATE, 'DD') ,
             p_period IN BM_CNT_PERIOD.CNT_PERIOD_ID%TYPE
             )
             RETURN NUMBER
  IS
    v_period BM_CNT_PERIOD%ROWTYPE;
  BEGIN
    select * into v_period from BM_CNT_PERIOD where CNT_PERIOD_ID = p_period;
    RETURN period_count(p_date_from, p_date_next, v_period.UNIT, v_period.VALUE, v_period.ADJUST);
  END;
  FUNCTION period_count (
             p_date_from IN DATE DEFAULT TRUNC(SYSDATE, 'DD') ,
             p_date_next IN DATE DEFAULT TRUNC(SYSDATE, 'DD') ,
             p_period IN NUMBER
             )
             RETURN NUMBER
  IS
    v_period BM_CNT_PERIOD%ROWTYPE;
  BEGIN
    select * into v_period from BM_CNT_PERIOD where CNT_PERIOD_ID = p_period;
    RETURN period_count(p_date_from, p_date_next, v_period.UNIT, v_period.VALUE, v_period.ADJUST);
  END;
  FUNCTION period_count (
             p_date_from IN DATE DEFAULT TRUNC(SYSDATE, 'DD') ,
             p_date_next IN DATE DEFAULT TRUNC(SYSDATE, 'DD') ,
             p_period IN VARCHAR2 := 'M' ,
             p_period_value IN NUMBER := 1 ,
             p_period_adjust IN NUMBER := 0
             )
             RETURN NUMBER
  IS
    d1 DATE; d2 DATE;
  BEGIN
    d1 := trunc(least(p_date_from, p_date_next));
    d2 := trunc(greatest(p_date_from, p_date_next));
    IF (p_period = 'D') THEN
      RETURN (d2 - d1) / p_period_value;
    ELSIF (p_period = 'W') THEN
      IF (p_period_adjust > 0) THEN
        d1 := trunc(d1, 'IW') + p_period_adjust - 1;
      END IF;
      RETURN (d2 - d1) / p_period_value / 7;
    ELSIF (p_period = 'M') THEN
      IF (p_period_adjust > 0) THEN
        d1 := trunc(d1, 'MM') + p_period_adjust - 1;
      END IF;
      RETURN months_between(d2+1, d1+1) / p_period_value;
    ELSIF (p_period = 'Y') THEN
      IF (p_period_adjust > 0) THEN
        d1 := add_months(trunc(d1, 'YYYY'), p_period_adjust - 1);
      END IF;
      d2 := trunc(d2, 'MM');
      RETURN months_between(d2, d1) / p_period_value / 12;
    ELSE
      RETURN null;
    END IF;
  END;



Схитрил так: months_between(d2+1, d1+1)
Вероятно нужно будет добавить проверку (не хитрить, если обе даты меньше последнего дня месяца), но все равно получилось намного компактнее.
Может быть я что-то упускаю?
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101857
SQL*Plus
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.,

Использовать функции MONTHS_BETWEEN и ADD_MONTHS, согласовав это с ответственными представителями бизнеса.
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101859
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Так не выйдет.
Месяцы считаются в биллинговой системе.
Мне же нужно сделать отчет, который будет учитывать месяцы точно так, как их учитывает биллинговая система.
Как именно это она делает — я выяснил, теперь нужно воспроизвести в SQL.
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101879
Фотография Stax
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Alibek B.

Как именно это она делает — я выяснил, ...


иногда потом оказывается, что (напр. в высокосном году), если ...

ps
использовать напрямую ф-цию из билинга не разрешают?

.....
stax
...
Рейтинг: 0 / 0
Количество месяцев между датами
    #40101905
Alibek B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Функция в биллинге - это функция на Perl с несколькими условиями. Внешнего API у биллинга нет.
...
Рейтинг: 0 / 0
22 сообщений из 22, страница 1 из 1
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Количество месяцев между датами
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]