oracle-databaseplsqlwhere-clauseto-datesysdate

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


I need to pick the data of expired credit cards for customers. As you know, the expiration date of a card only uses month in MM format and year in YY format. In my database all of the expiration dates are stored in MMYY format, so I use TO_DATE(FONDOS.VENCTARJETA, 'MMYY') to get a date and then to apply some conditions.

This is my Query:

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)

This is returning 89 rows like these:

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 ----

With the results, we notice there are no errors when converting 'MMYY' date into a date type column.

As you can see, I commented a condition in where clause, then if I uncomment the line I get this:

ORA-01841: (full) year must be between -4713 and +9999, and not be 0

This is not the only behaviour where I am getting this error, but this was the simplest I got to show you how it fails.

It has no logic, I can't find why it happens. Please, help. Thanks.


Solution

  • The filter conditions can be executed in any order Oracle decides is best. It suggests that you have some rows in your table that do not properly convert to a date using that particular format mask, but do get filtered out by your join condition. When you include the filter, Oracle probably sees that it can prefilter on your datos_fondospol table before joining to your other table, at which point every row will hit the function.

    If you are on at least Oracle version 12.2 you can identify all the rows that contain data that can't be converted to a date with that format mask with validate_conversion:

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

    If this data is correct but can safely be ignored then you can use another 12.2 addition:

    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)
    

    If you are only on 12.1 then you can make a similar function yourself with a with plsql clause:

    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)
    

    If you are on less than that then you can make the PL/SQL function explicitly and call it. Or you could manufacture a case expression to check the contents of your string first.

    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)