【问题标题】:ORA-01841 Error when aplying conditions on date in where clauseORA-01841 在 where 子句中应用日期条件时出错
【发布时间】:2020-11-01 17:48:48
【问题描述】:

我需要为客户挑选过期信用卡的数据。 如您所知,卡的有效期仅使用MM格式的月份和YY格式的年份。在我的数据库中,所有到期日期都以 MMYY 格式存储,因此我使用 TO_DATE(FONDOS.VENCTARJETA, 'MMYY') 获取日期,然后应用一些条件。

这是我的查询:

SELECT 
    TO_DATE(FONDOS.VENCTARJETA, 'MMYY') AS F_VENCIMIENTO
FROM 
    POLIZA POLIZA,
    DATOS_FONDOSPOL FONDOS
WHERE
    POLIZA.IDEPOL = FONDOS.IDEPOL AND
    --TO_DATE(FONDOS.VENCTARJETA, 'MMYY') <= SYSDATE AND
    POLIZA.CODINTER = TO_NUMBER(:P2_CLAVE)

这将返回 89 行,如下所示:

F_VENCIMIENTO      |
-------------------|
                   |
2023-08-01 00:00:00|
2020-08-01 00:00:00|
2021-11-01 00:00:00|
2020-09-01 00:00:00|
                   |
2023-02-01 00:00:00|
---- many more ----

根据结果,我们注意到将“MMYY”日期转换为日期类型列时没有错误。

如您所见,我在 where 子句中注释了一个条件,然后如果我取消注释该行,我会得到:

ORA-01841:(完整)年份必须介于 -4713 和 +9999 之间,并且不能为 0

这不是我收到此错误的唯一行为,但这是我向您展示它如何失败的最简单的行为。

它没有逻辑,我找不到它发生的原因。 请帮忙。 谢谢。

【问题讨论】:

  • F_VENCIMIENTO是什么类型?
  • 除了您的问题(您已经有两个很好的答案)之外,请考虑该方法中这两个额外的潜在逻辑错误。首先,如果由于某种原因您仍然拥有 1998 年到期的卡(例如),它们的到期日期可能为“1098”。如果您的数据中可能存在这种到期日期,您应该使用'RR' 格式模型而不是'YY' 表示年份。另外,一张 0920 到期的卡不会在 2020 年 9 月 1 日到期 - 它会在 2020 年 10 月 1 日到期。您需要在 WHERE 条件下进行为期一个月的调整。使用 ADD_MONTHS 函数。

标签: oracle plsql where-clause to-date sysdate


【解决方案1】:

过滤条件可以按照 Oracle 认为最好的任何顺序执行。它表明您的表中有一些行没有使用该特定格式掩码正确转换为日期,但确实被您的连接条件过滤掉了。当您包含过滤器时,Oracle 可能会看到它可以在加入您的另一个表之前对您的 datos_fondospol 表进行预过滤,此时每一行都会命中该函数。

如果您至少使用 Oracle 12.2 版,则可以使用 validate_conversion 识别所有包含无法转换为具有该格式掩码的日期的数据的行:

select 
from   datos_fondospol 
where  validate_conversion(venctarjeta as date, 'MMYY') = 0

如果此数据正确但可以安全地忽略,那么您可以使用另一个 12.2 添加:

SELECT 
    TO_DATE(FONDOS.VENCTARJETA, 'MMYY') AS F_VENCIMIENTO
FROM 
    POLIZA POLIZA,
    DATOS_FONDOSPOL FONDOS
WHERE
    POLIZA.IDEPOL = FONDOS.IDEPOL AND
    TO_DATE(FONDOS.VENCTARJETA default null on conversion error, 'MMYY') <= SYSDATE AND
    POLIZA.CODINTER = TO_NUMBER(:P2_CLAVE)

如果您只使用 12.1,那么您可以使用 with plsql 子句自己制作类似的功能:

with 
 function default_date(dateString varchar2,dateFormat varchar2)
 return date
 is
   convertedDate  date;
 begin
   convertedDate := to_date(dateString,dateFormat );
   return convertedDate ;
 exception when others then
   return null;
 end;
SELECT 
    TO_DATE(FONDOS.VENCTARJETA, 'MMYY') AS F_VENCIMIENTO
FROM 
    POLIZA POLIZA,
    DATOS_FONDOSPOL FONDOS
WHERE
    POLIZA.IDEPOL = FONDOS.IDEPOL AND
    default_date(FONDOS.VENCTARJETA, 'MMYY') <= SYSDATE AND
    POLIZA.CODINTER = TO_NUMBER(:P2_CLAVE)

如果你的时间少于那个值,那么你可以显式地创建 PL/SQL 函数并调用它。或者你可以制造一个case 表达式来首先检查你的字符串的内容。

SELECT 
    TO_DATE(FONDOS.VENCTARJETA, 'MMYY') AS F_VENCIMIENTO
FROM 
    POLIZA POLIZA,
    DATOS_FONDOSPOL FONDOS
WHERE
    POLIZA.IDEPOL = FONDOS.IDEPOL AND
    case when regexp_like (FONDOS.VENCTARJETA, '^[0-9]{4}$')
 and to_number(substr(FONDOS.VENCTARJETA,1,2)) between 1 and 12  
 then to_date(FONDOS.VENCTARJETA, 'MMYY') else cast(null as date) end <= SYSDATE AND
    POLIZA.CODINTER = TO_NUMBER(:P2_CLAVE)

【讨论】:

  • 这是有道理的,但有一个例外(我认为)。 OP声明了一个非常具体的错误,与年份必须在界限内而不是零有关。当在to_date 中使用格式模型'MMYY' 时,我想不出任何(无效)字符串会引发该精确错误。那可能是什么?
  • 如果您有字母应该是年份编号,您可能会收到该错误:select to_date('01AA','MMYY') from dual; 给出此错误dbfiddle.uk/…
  • 哦!你是对的 - 我能够复制。有趣的选择(在这种情况下要抛出的特定错误)。
  • 谢谢@AndrewSayer,validate_conversion 函数对我帮助很大,我不知道。有两行数据格式不同于 MMYY,均为 MM/YY 格式。我将TO_DATE(FONDOS.VENCTARJETA, 'MMYY') 更改为TO_DATE(FONDOS.VENCTARJETA, 'MM/YY'),它似乎适用于“MM/YY”和“MMYY”格式。
【解决方案2】:

DATOS_FONDOSPOL 中的VENCTARJETA 列中至少有一行无法将字符串转换为有效日期。如果您查看带有和不带有注释条件的查询计划,我的猜测是,当条件存在时,Oracle 在 CODINTER 谓词之前和/或在连接过滤掉包含无效数据的行之前应用 sysdate 谓词.如果您的计划恰好在应用to_date 函数之前过滤掉了坏数据,那么查询似乎可以工作。但是,如果某些事情导致计划发生变化,并且在过滤掉不良数据之前应用了to_date 函数,则会出现错误。

【讨论】:

    【解决方案3】:

    已经尝试过一个用例,不确定这是否是您要寻找的

    --- 使用示例数据创建表--------------

    CREATE TABLE POLIZA(IDPOL 编号,VENCTARJETA 日期);

    CREATE TABLE DATOS_FONDOSPOL(IDPOL 编号, X_EXTRA varchar2(10));

    插入 POLIZA 值 (2001,to_date('0816','MM/YY')); 插入 POLIZA 值 (2002,to_date('1016','MM/YY'));

    插入 DATOS_FONDOSPOL 值(2002 , 'zzzz'); INSERT INTO DATOS_FONDOSPOL 值(2001 , 'zzzz');


    下面的选择对我来说很好,我所做的只是使用 TO_CHAR

    SELECT 
       TO_CHAR(POLIZA.VENCTARJETA, 'MMYY')AS F_VENCIMIENTO
    FROM 
        POLZIA POLIZA,
        DATOS_FONDOSPOL FONDOS
    WHERE
        POLIZA.IDPOL = FONDOS.IDPOL 
        AND TO_CHAR(POLIZA.VENCTARJETA, 'MMYY') <= TO_CHAR(SYSDATE-120, 'MMYY');
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-03
      • 2014-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-29
      相关资源
      最近更新 更多