Сравнение дат в Oracle, чтобы определить, является ли сегодня последний рабочий день месяца

Я использую Oracle 11g.

Итак, у меня есть этот запрос, который дает мне Last Business Day of a Month (всегда приветствуются любые предложения по улучшению запроса)

select DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
              to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
              to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
              to_char(last_day(sysdate), 'DD-MON-YYYY'))
  into LAST_BD_OF_MONTH_P
  from dual;

Что на сегодняшний день дает мне этот результат

30-APR-2015

Теперь, когда я сравниваю это с sysdate + 9, чтобы проверить, является ли это концом месяца, он всегда дает мне No Match - см., что я конвертирую оба в дату, используя to_date.

 select to_char(sysdate + 9,'DD-MON-YYYY')
        , DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
          to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
          to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
          to_char(last_day(sysdate), 'DD-MON-YYYY')) as EOMBD
        , case when to_date(sysdate + 9,'DD-MON-YYYY') = 
                    to_date(DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
                    to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
                    to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
                    to_char(last_day(sysdate), 'DD-MON-YYYY')), 'DD-MON-YYYY') 
               then 'Match' 
               else 'No Match' 
          end as Match
    from dual;

Но если я изменю этот запрос, чтобы преобразовать sysdate + 9 в char с использованием to_char, он сработает и даст мне Match.

 select to_char(sysdate + 9,'DD-MON-YYYY')
        , DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
          to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
          to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
          to_char(last_day(sysdate), 'DD-MON-YYYY')) as EOMBD
        , case when /*convert using to_char*/to_char(sysdate + 9,'DD-MON-YYYY') = 
                    DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
                    to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
                    to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
                    to_char(last_day(sysdate), 'DD-MON-YYYY')) 
               then 'Match' 
               else 'No Match' 
          end as Match
    from dual;

Я понимаю, что во втором запросе он соответствует strings и, таким образом, дает мне Match. Есть ли способ, которым это сравнение дает мне результат Match без преобразования этого в char?

Это правда, что это дает мне желаемый результат, но я бы не хотел использовать здесь функцию to_char.

PS: LAST_BD_OF_MONTH_P объявляется как DATE

ВКЛЮЧЕН ОТВЕТ

 select to_char(sysdate + 9,'DD-MON-YYYY'),  DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
      to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
      to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
      to_char(last_day(sysdate), 'DD-MON-YYYY')) as EOMBD
    , case when trunc(sysdate + 9) = 
                to_date(DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
                to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
                to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
                to_char(last_day(sysdate), 'DD-MON-YYYY')), 'DD-MON-YYYY') 
           then 'Match' 
           else 'No Match' 
      end as Match
from dual;

person Saagar Elias Jacky    schedule 21.04.2015    source источник
comment
Какой у вас NLS_DATE_FORMAT? Вы полагаетесь на это, чего не следует делать, но в данный момент я получаю различные ошибки из обоих ваших запросов - зная, что ваши настройки могут позволить им работать, по крайней мере, в качестве отправной точки. Почему вы хотите, чтобы LAST_BD_OF_MONTH_P был строкой, а не типом даты?   -  person Alex Poole    schedule 21.04.2015
comment
@AlexPoole DD-MON-RR, также LAST_BD_OF_MONTH_P объявляется как Date   -  person Saagar Elias Jacky    schedule 21.04.2015
comment
Надеюсь, это поможет lalitkumarb.wordpress.com/2015/04/15/   -  person Lalit Kumar B    schedule 21.04.2015
comment
Хорошо, я все еще получаю сообщение ORA-01722: неверный номер из вашего последнего запроса.   -  person Alex Poole    schedule 21.04.2015
comment
to_date(sysdate) бесполезен. Он преобразует date в varchar только для того, чтобы снова преобразовать его обратно в date.   -  person a_horse_with_no_name    schedule 21.04.2015
comment
Следует отметить, что результат to_char(..., 'D') зависит от текущих настроек NLS_TERRITORY, которые могут меняться в каждом сеансе.   -  person Wernfried Domscheit    schedule 21.04.2015


Ответы (3)


Это делает неявное преобразование sysdate + 9 в строку, используя ваш NLS_DATE_FORMAT; а затем преобразование обратно в дату:

case when to_date(sysdate + 9,'DD-MON-YYYY') = 

В лучшем случае это сработает, если ваш формат также DD-MON-YYYY, но в этом случае вы теряете столетие, потому что вы действительно делаете:

case when to_date(to_char(sysdate + 9, 'DD-MON-RR'),'DD-MON-YYYY') = 

Несоответствие RR и YYYY означает, что вы получите год 0015, а не 2015.

Вы можете упростить это до:

case when trunc(sysdate) + 9 = 

Итак, только с этим изменением:

 select to_char(sysdate + 9,'DD-MON-YYYY')
        , DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
          to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
          to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
          to_char(last_day(sysdate), 'DD-MON-YYYY')) as EOMBD
        , case when trunc(sysdate + 9) = 
                    to_date(DECODE(to_char(last_day(to_date(sysdate)), 'D'),'7',
                    to_char((last_day(sysdate) - 1), 'DD-MON-YYYY'),'1',
                    to_char((last_day(sysdate) - 2), 'DD-MON-YYYY'),
                    to_char(last_day(sysdate), 'DD-MON-YYYY')), 'DD-MON-YYYY') 
               then 'Match' 
               else 'No Match' 
          end as Match
    from dual;

TO_CHAR(SYSDATE+9,'DD-MON-YYYY') EOMBD                MATCH  
-------------------------------- -------------------- --------
30-APR-2015                      30-APR-2015          Match   

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

person Alex Poole    schedule 21.04.2015

К сожалению, ваша логика не совсем точна. Возьмем, к примеру, март 2013 года:

  with w_date as ( select to_date('15-mar-2013','dd-mon-yyyy') d from dual )
  select DECODE(to_char(last_day(d), 'D'),'7',
                to_char((last_day(d) - 1), 'DD-MON-YYYY'),'1',
                to_char((last_day(d) - 2), 'DD-MON-YYYY'),
                to_char(last_day(d), 'DD-MON-YYYY'))
    from w_date;

  DECODE(TO_C
  -----------
  29-MAR-2013

Это Страстная пятница... так что "нерабочий день"... должно быть "28 марта 2013"

Не делайте так много преобразований to_date/to_char .. это вас огорчит.

Чтобы делать такие вещи, вам действительно нужна таблица праздников (или таблица рабочих дней - в любом случае). Когда у вас это есть, решение становится тривиальным:

Если у вас есть таблица праздников:

  with w_date as ( select to_date('15-mar-2013','dd-mon-yyyy') d from dual ),
       w_holidays as (
       select to_date('29-mar-2013','dd-mon-yyyy') holiday from dual
          ),
      w_sub as (
           select last_day(d) - level + 1  dd
             from w_date
             connect by level <= 10
        )
  select max(dd)
    from w_sub s
    where to_char(dd,'d') not in ( 1,7)
      and not exists ( select * from w_holidays h
                        where h.holiday = s.dd )

  /

полученные результаты:

  MAX(DD)
  ---------
  28-MAR-13

  1 row selected.

Если у вас есть таблица рабочих дней:

  with w_business_days as (
       select to_date('25-mar-2013','dd-mon-yyyy') busday from dual union all
       select to_date('26-mar-2013','dd-mon-yyyy') busday from dual union all
       select to_date('27-mar-2013','dd-mon-yyyy') busday from dual union all
       select to_date('28-mar-2013','dd-mon-yyyy') busday from dual union all
       select to_date('01-apr-2013','dd-mon-yyyy') busday from dual
          )
  select max(busday)
    from w_business_days
   where busday <= last_day(to_date('15-mar-2013','dd-mon-yyyy') )
  /

полученные результаты:

  MAX(BUSDA
  ---------
  28-MAR-13

  1 row selected.
person Ditto    schedule 21.04.2015
comment
Вы уверены, что это работает? Что произойдет, если вы замените дату на 30-APR-2015? w_holidays будет нулевым, а результат будет 29-APR-2015 - person Saagar Elias Jacky; 22.04.2015
comment
Кроме того, меня больше беспокоят праздничные дни, а не обычные праздники... :-) - person Saagar Elias Jacky; 22.04.2015
comment
@Saagar: ааа, извините, небольшая ошибка: добавлен +1 в w_sub для праздничного запроса (он не учитывал фактический последний день, в случае с апрелем 2013 года, это 30 апреля), и изменено ‹ на ‹= в бизнесе дневной запрос по той же причине. Прошу прощения за это :) А что касается государственных праздников... не беспокойтесь, просто заполните праздничную таблицу тем, что хотите, чтобы это выглядело как праздник... например, 11 сентября 2001 года... в некоторых случаях. :( - person Ditto; 24.04.2015

Я предлагаю использовать календарь DBMS_SCHEDULER. Вы можете создать такую ​​функцию:

CREATE OR REPLACE FUNCTION LAST_BD_OF_MONTH_P(the_day IN TIMESTAMP) RETURN TIMESTAMP AS
    next_run_date TIMESTAMP;
BEGIN
    DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=MONTHLY; BYDAY=-1 FRI', NULL, the_day, next_run_date);
    RETURN next_run_date;
END;

SELECT CAST(LAST_BD_OF_MONTH_P(DATE '2015-05-10') AS DATE) FROM dual;
=====================================
2015-05-29

Синтаксис календаря см. здесь: Синтаксис календаря

Обратите внимание: когда вы указываете дату после последнего рабочего дня (например, 2015-04-30), эта функция вернет последний рабочий день следующего месяца (например, 2015-06-26).

Чтобы избежать этого, вы можете использовать

DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=MONTHLY; BYDAY=-1 FRI', NULL, TRUNC(the_day, 'MM'), next_run_date); вместо этого.

Чтобы охватить также праздничные дни, взгляните на один из них:

Расчетная дата выполнения Дата начала и часы работы/обеда PL/SQL

Sql: разница между двумя датами

person Wernfried Domscheit    schedule 21.04.2015