Как обработать исключения to_date в инструкции SELECT, чтобы игнорировать эти строки? - PullRequest
8 голосов
/ 11 мая 2011

У меня есть следующий запрос, который я пытаюсь использовать в качестве КОМАНДЫ в отчете Crystal, над которым я работаю.

SELECT * FROM myTable
WHERE to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}

Это работает нормально, однако меня беспокоит только то, что дата не можетвсегда быть в правильном формате (из-за ошибки пользователя).Я знаю, что при сбое функции to_date возникает исключение .. возможно ли обработать это исключение таким образом, что оно игнорирует соответствующую строку в моем операторе SELECT?Потому что иначе мой отчет сломался бы, если бы только одна дата во всей базе данных была неправильно отформатирована.

Я посмотрел, предлагает ли Oracle функцию isDate, но кажется, что вы должны просто обработать исключение.Любая помощь будет принята с благодарностью.Спасибо !!

Ответы [ 5 ]

25 голосов
/ 11 мая 2011

Повторяя комментарии Тони, вам было бы гораздо лучше хранить даты в столбцах DATE, чем заставлять интерфейсный инструмент запросов находить и обрабатывать эти исключения.

Однако, если вы застряли с неверной моделью данных, самый простой вариант - создать функцию, которая выполняет преобразование и обрабатывает ошибку,

CREATE OR REPLACE FUNCTION my_to_date( p_date_str IN VARCHAR2,
                              p_format_mask IN VARCHAR2 )
  RETURN DATE
IS
  l_date DATE;
BEGIN
  l_date := to_date( p_date_str, p_format_mask );
  RETURN l_date;
EXCEPTION
  WHEN others THEN
    RETURN null;
END my_to_date;

Ваш запрос станет

SELECT * 
  FROM myTable
 WHERE my_to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}

Конечно, вы, скорее всего, захотите индекс на основе функции при вызове MY_TO_DATE, чтобы сделать этот запрос достаточно эффективным.

5 голосов
/ 11 мая 2011

Если ваши данные не согласованы и даты, хранящиеся в виде строк, могут быть недействительными, у вас есть 3 варианта.

  1. Выполните рефакторинг вашей БД, чтобы убедиться, что в столбце хранится тип данных даты
  2. Обрабатывать исключение строки до даты в хранимой процедуре
  3. Обрабатывать исключение строки до даты в (сложной) формуле выбора записи

Я бы предложил использовать первый вариант, поскольку ваши данные должны быть согласованы.

Второй вариант обеспечит некоторую гибкость и скорость, поскольку отчет будет выбирать только те строки, которые необходимы.

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

4 голосов
/ 12 мая 2011

У меня та же проблема ... старая унаследованная база данных с полями varchar для дат и десятилетий неверных данных в поле. Как бы я ни хотел, я также не могу изменить типы данных. Но я придумала это решение, чтобы выяснить, является ли дата текущей, что, похоже, и то, что вы делаете:

select * from MyTable
where regexp_like(sdate, '[0-1][0-9].[0-3][0-9].[0-9][0-9][0-9][0-9]')
         -- make sure it's in the right format and ignore rows that are not
and substr(sdate,7,10) || substr(sdate,1,2) || substr(sdate,4,5) >= to_char({?EndDate}, 'YYYYMMDD')
         -- put the date in ISO format and do a string compare

Преимущество такого подхода в том, что он не задыхается от таких дат, как "30 февраля".

2 голосов
/ 12 мая 2011

Поскольку вы говорите, что у вас нет доступа к базе данных, я предполагаю, что вы не можете создать какие-либо функции, которые могли бы помочь вам в этом, и что вы можете только выполнять запросы?

Если это так, то следующий код должен получить большую часть того, что вам нужно, с учетом следующих предостережений: 1) Сохраненный формат даты, который вы хотите оценить, - «мм / дд / гггг». Если это не так, то вы можете изменить код в соответствии с вашим форматом. 2) База данных не содержит недопустимых дат, таких как 30 февраля.

Сначала я создал свою тестовую таблицу и тестовые данные:

create table test ( x number, sdate varchar2(20));
insert into test values (1, null);
insert into test values (2, '01/01/1999');
insert into test values (3, '1999/01/01');
insert into test values (4, '01-01-1999');
insert into test values (5, '01/01-1999');
insert into test values (6, '01-01/1999');
insert into test values (7, '12/31/1999');
insert into test values (8, '31/12/1999');
commit;

Теперь запрос:

WITH dates AS (
    SELECT x
         , sdate
         , substr(sdate,1,2) as mm
         , substr(sdate,4,2) as dd
         , substr(sdate,7,4) as yyyy
    FROM test
    WHERE ( substr(sdate,1,2) IS NOT NAN -- make sure the first 2 characters are digits
            AND to_number(substr(sdate,1,2))  between 1 and 12 -- and are between 0 and 12
            AND substr(sdate,3,1) = '/' -- make sure the next character is a '/'
            AND substr(sdate,4,2) IS NOT NAN -- make sure the next 2 are digits
            AND to_number(substr(sdate,4,2)) between 1 and 31 -- and are between 0 and 31
            AND substr(sdate,6,1) = '/' -- make sure the next character is a '/'
            AND substr(sdate,7,4) IS NOT NAN -- make sure the next 4 are digits
            AND to_number(substr(sdate,7,4)) between 1 and 9999 -- and are between 1 and 9999
          )
)
SELECT x, sdate
FROM dates
WHERE to_date(mm||'/'||dd||'/'||yyyy,'mm/dd/yyyy') <= to_date('08/01/1999','mm/dd/yyyy');

И мои результаты:

X  SDATE
-  ----------
2  01/01/1999

Оператор WITH выполнит большую часть проверки, чтобы убедиться, что значения sdate, по крайней мере, имеют правильный формат. Мне приходилось прерывать каждую единицу времени месяц / день / год, чтобы выполнить оценку to_date, потому что я все еще получал ошибку недопустимого месяца, когда выполнял to_date в sdate.

Надеюсь, это поможет.

1 голос
/ 07 марта 2018

Доверяй, этот ответ уточняет ... для недопустимой даты нет прямого ИСКЛЮЧИТЕЛЯ ИСКЛЮЧЕНИЯ. Один простой способ приведен ниже, если вы знаете формат, например, ДД / ММ / ГГГГ, а затем приведенная ниже функция REGEXP_LIKE будет работать как чудо. to_date() также будет работать, когда найдена invalid_date, курсор переместится на OTHERS EXCEPTION. приведены ниже.

DECLARE
   tmpnum      NUMBER; -- (1=true; 0 = false)
   ov_errmsg   LONG;
   tmpdate     DATE;
   lv_date     VARCHAR2 (15);
BEGIN
   lv_date := '6/2/2018'; -- this will fail in *regexp_like* itself
   lv_date := '06/22/2018'; -- this will fail in *to_date* and will be caught in *exception WHEN OTHERS* block
   lv_date := '07/03/2018'; -- this will succeed 

   BEGIN
      tmpnum := REGEXP_LIKE (lv_date, '[0-9]{2}/[0-9]{2}/[0-9]{4}');

      IF tmpnum = 0
      THEN                                              -- (1=true; 0 = false)
         ov_errmsg := '1. INVALID DATE FORMAT ';
         DBMS_OUTPUT.PUT_LINE (ov_errmsg);
         RETURN;
      END IF;

      tmpdate := TO_DATE (lv_date, 'DD/MM/RRRR');
      --tmpdate := TRUNC (NVL (to_date(lv_date,'DD/MM/RRRR'), SYSDATE));

      tmpnum := 1;
   EXCEPTION
      WHEN OTHERS
      THEN
         BEGIN
            tmpnum := 0;
            ov_errmsg := '2. INVALID DATE FORMAT ';
            DBMS_OUTPUT.PUT_LINE (ov_errmsg || SQLERRM);
            RETURN;
         END;
   -- continue with your other query blocks
   END;

   -- continue with your other query blocks
   DBMS_OUTPUT.PUT_LINE (tmpnum);
END;
...