【问题标题】:How to handle to_date exceptions in a SELECT statment to ignore those rows?如何处理 SELECT 语句中的 to_date 异常以忽略这些行?
【发布时间】:2015-07-19 17:35:11
【问题描述】:

我正在尝试将以下查询用作我正在处理的水晶报表中的命令。

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

这很好用,但我唯一担心的是日期可能并不总是正确的格式(由于用户错误)。我知道当 to_date 函数失败时,它会引发异常。是否可以以忽略我的 SELECT 语句中的相应行的方式处理此异常?因为否则如果整个数据库中只有一个日期格式不正确,我的报告就会中断。

我查看了 Oracle 是否提供 isDate 函数,但您似乎应该只处理异常。任何帮助将不胜感激。谢谢!!

【问题讨论】:

  • 好吧,您应该将日期存储在 DATE 列中。你为什么不呢?
  • 这是我在工作中使用的遗留应用程序。他们使用字符串,否则日期默认为当前日期......我无法控制数据库
  • 如果您在 varchar2 字段中有日期(错误)并且您甚至没有一致的格式,那么您可能是 SOL(没有重建表格以将所有各种 char 格式转换为实际的 DATE字段)
  • 好吧,如果您在GUI 上收集DATE,它将不会具有DATE 格式...幸运的是,Oracle 现在有了ON CONVERSION ERROR 子句。

标签: oracle crystal-reports


【解决方案1】:

呼应 Tony 的评论,最好将日期存储在 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 调用上使用基于函数的索引,以使此查询相当有效。

在 12.2 中,Oracle 为 to_datecast 函数添加了扩展,以处理出现错误的转换

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

如果您要查找所有有效日期(或无效)的行,也可以使用validate_conversion 函数。

SELECT *
  FROM myTable 
 WHERE validate_conversion( myTable.sdate as date, 'MM/DD/YYYY' ) = 1 

【讨论】:

  • 被评为最佳实践并修复“遗留”(即损坏)系统
  • +1 旧系统已成为现实。我们必须尽我们所能与他们打交道。
【解决方案2】:

从 Oracle 12c 开始,无需定义函数来捕获转换异常。

Oracle 在TO_DATE 函数中引入了ON CONVERSION ERROR 子句。

该子句基本上抑制了转换无效日期字符串的错误(典型错误为ORA-01843, ORA-01841, ORA-011861, ORA-01840)并返回指定的默认值或null。

使用示例

select to_date('2020-99-01','yyyy-mm-dd') from dual;
-- ORA-01843: not a valid month
select to_date('2020-99-01' default null on conversion error,'yyyy-mm-dd') from dual;
-- returns NULL
select to_date('2020-99-01' default '2020-01-01' on conversion error,'yyyy-mm-dd') from dual;
-- 01.01.2020 00:00:00

旧版应用解决方案

假设有一个表,其 date 列存储为 VARCHAR2(10)

select  * from tab;

DATE_CHAR 
----------
2021-01-01
2021-99-01

使用上述功能定义了一个VIRTUAL DATE 列,在转换错误

的情况下显示日期或NULL
alter table tab add (
  date_d DATE as (to_date(date_char default null on conversion error,'yyyy-mm-dd')) VIRTUAL
);

select * from tab;

DATE_CHAR  DATE_D             
---------- -------------------
2021-01-01 01.01.2021 00:00:00
2021-99-01  

VIRTUAL 列可以安全使用,因为它的格式是DATE,如果需要,可以在其上设置索引。

 select  * from tab where date_d = date'2021-01-01';

【讨论】:

    【解决方案3】:

    相信这个回复澄清了...... 无效日期没有直接的异常处理程序。 一旦您知道像 DD/MM/YYYY 这样的格式,下面给出了一种简单的方法,然后下面给出的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;
    

    【讨论】:

      【解决方案4】:

      我有同样的问题...一个旧的旧数据库,其中包含日期的 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
      

      这种方法的好处是它不会在像“2 月 30 日”这样的日期阻塞。

      【讨论】:

      • 我喜欢最后的 “功能不是错误” 评论。我建议regex 不是解决这个问题的方法,除非你整个人(?&lt;!\d)(?:(?:(?:0?[1-9]|[12][0-9]|30)\W(?:0?[13-9]|1[0-2])|31\W(?:0?[13578]|1[02])|(?:[0-2]?[1-8]|[12]0|[01]?9)-0?2)\W\d{1,4}|29\W0?2\W\d{0,2}(?:\d?0|[13579][26]|[02468][48]))(?!\d)... 这是相对完整的,但是有一些捷径,比如\W类似于[:/ -]
      【解决方案5】:

      既然你说你“无权访问”数据库,我假设你不能创建任何函数来帮助你,你只能运行查询?

      如果是这种情况,那么以下代码应该可以满足您的大部分需求,但需要注意以下几点: 1) 您要评估的存储日期格式是“mm/dd/yyyy”。如果不是这种情况,那么您可以更改代码以适合您的格式。 2) 数据库中不包含无效日期,例如 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 评估,因为当我在 sdate 上执行 to_date 时仍然收到无效的月份错误。

      我希望这会有所帮助。

      【讨论】:

        【解决方案6】:

        如果您的数据不一致并且存储为字符串的日期可能无效,那么您有 3 个选项。

        1. 重构您的数据库以确保该列存储日期数据类型
        2. 处理存储过程中字符串到日期的异常
        3. 在(复杂的)记录选择公式中处理字符串到日期的异常

        我建议使用第一个选项,因为您的数据应该是一致的。

        第二个选项将提供一些灵活性和速度,因为报告只会获取所需的行。

        第三个选项将强制报告获取表中的每条记录,然后让报告过滤记录。

        【讨论】:

        • 嘿,谢谢您的回复.. 不幸的是,我无法控制数据库...是的,我在考虑选项 3.. 但我不想支付所涉及的开销..我将首先研究 2.. 过去我的 ODBC 和存储过程有问题.. 但我会再次检查一下
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多