ORA-01841 在 where 子句中对日期应用条件时出错

ORA-01841 Error when aplying conditions on date in where clause

我需要为客户提取过期信用卡的数据。 如您所知,卡片的到期日期仅使用 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: (full) year must be between -4713 and +9999, and not be 0

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

没有逻辑,我找不到为什么会这样。 请帮忙。 谢谢

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

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

如果您使用的至少是 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)

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

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

创建 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'); 插入 DATOS_FONDOSPOL 值(2001 年,'zzzz');


下面的select对我来说很好,我只是用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');