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');
我需要为客户提取过期信用卡的数据。 如您所知,卡片的到期日期仅使用 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');