Search code examples
oracle-databaseplsqlora-06502

PL/SQL: numeric or value errors


I'm getting an error and I don't know why:

ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.STANDARD", line 394
ORA-06512: at "DOMINOS.DISTANCE", line 10
ORA-06512: at "DOMINOS.ZOEKWINKELVOORADRES", line 19
ORA-06512: at line 5

The cursor should contain 145 rows. When I execute the procedure I get the error message above after 54 rows.

create or replace procedure zoekWinkelVoorAdres
    (v_postcode in postcode.postcode%type,
      v_huisnr in WINKEL.HUISNR%type,
      v_id out WINKEL.ID%type,
      v_afstand out number)
is
    type lat_array is varray(100000) of POSTCODE.LAT%type;
    type lon_array is varray(100000) of POSTCODE.LON%type;
    type id_array is varray(100000) of winkel.id%type;
    a_lat lat_array;
    a_lon lon_array;
    a_id id_array;
    latwin postcode.lat%type;
    lonwin postcode.lon%type;
    latklant postcode.lat%type;
    lonklant postcode.lon%type;
    vafstand number(38);
    cursor winkelafstand is
        select w.ID, p.lat, p.lon 
        from winkel w 
        join postcode p 
        on w.POSTCODE_ID_FK = p.POSTCODE_ID;
begin
    select lat, lon into latklant,lonklant 
    from postcode 
    where postcode = v_postcode;
    open winkelafstand;
    fetch winkelafstand bulk collect into a_id, a_lat, a_lon;
    close winkelafstand;
    for i in a_lat.first..a_lat.last loop
        vafstand := distance(a_lat(i),a_lon(i),latklant,lonklant);
        dbms_output.put_line(vafstand || ' ' || a_id(i));
        insert into winkel_afstand
             (Winkel_ID, afstand) values(a_id(i),vafstand);
    end loop;
end;
/

Solution

  • From a bit of searching it looks like you get this error if you give the same same position for both sets of coordinates.

    Assuming your distance function is defined similarly to that linked example:

    CREATE OR REPLACE FUNCTION DISTANCE 
    ( 
    Lat1 IN NUMBER, 
    Lon1 IN NUMBER, 
    Lat2 IN NUMBER, 
    Lon2 IN NUMBER 
    ) RETURN NUMBER IS 
    DegToRad NUMBER := 57.29577951; 
    BEGIN
    RETURN(6387.7 * ACOS((sin(NVL(Lat1,0) / DegToRad) * SIN(NVL(Lat2,0) / DegToRad)) + 
    (COS(NVL(Lat1,0) / DegToRad) * COS(NVL(Lat2,0) / DegToRad) * 
    COS(NVL(Lon2,0) / DegToRad - NVL(Lon1,0)/ DegToRad)))); 
    END; 
    /
    

    ... then if you pass the same pair of values twice the calculation evaluates to something invalid due to rounding errors, e.g. with

    select distance(53.8662, 10.68117, 53.8662, 10.68117) from dual
    

    Adding debugs for the components (in the function, between BEGIN and RETURN) shows:

    dbms_output.put_line(lat1 ||','|| lon1);
    dbms_output.put_line(sin(NVL(Lat1,0) / DegToRad));
    dbms_output.put_line(SIN(NVL(Lat2,0) / DegToRad));
    dbms_output.put_line(COS(NVL(Lat1,0) / DegToRad));
    dbms_output.put_line(COS(NVL(Lat2,0) / DegToRad)); 
    dbms_output.put_line(COS(NVL(Lon2,0) / DegToRad));
    dbms_output.put_line(NVL(Lon1,0)/ DegToRad);
    
    .8076421638813717679360124563997362950201
    .8076421638813717679360124563997362950201
    .5896729051949185735939828069514084977347
    .5896729051949185735939828069514084977347
    .9826737619730074300608748352929523713616
    .1864215844752715888130518254292967904505
    

    and when those are multiplied and added together the result is:

    1.00000000000000000000000000000000000001
    

    So the whole thing evaluates to RETURN(6387.7 * ACOS(1.00000000000000000000000000000000000001)), and ACOS(1.00000000000000000000000000000000000001) throws the same error, at least in PL/SQL:

    declare
      result number;
    begin
      result := acos(1.00000000000000000000000000000000000001);
    end;
    /
    
    ORA-06502: PL/SQL: numeric or value error
    ORA-06512: at "SYS.STANDARD", line 394
    ORA-06512: at line 4
    

    The SQL function gets a different error:

    select acos(1.00000000000000000000000000000000000001) from dual;
    
    SQL Error: ORA-01428: argument '1.00000000000000000000000000000000000001' is out of range
    

    ... but it's the same problem, it isn't meaningful to pass a value greater than 1 to ACOS.

    As a workaround you could change the function to ROUND() the value before calling ACOS(), with a high enough argument to not significantly affect other calculations, though as with any rounding it won't be perfect (but clearly isn't anyway!):

      RETURN(6387.7 * ACOS(ROUND((
        (SIN(NVL(Lat1,0) / DegToRad) * SIN(NVL(Lat2,0) / DegToRad))
          + (COS(NVL(Lat1,0) / DegToRad)
            * COS(NVL(Lat2,0) / DegToRad)
            * COS(NVL(Lon2,0) / DegToRad - NVL(Lon1,0)/ DegToRad)
            )
        ), 9))
      ); 
    

    With that change:

    select distance(53.8662, 10.68117, 53.8662, 10.68117) from dual;
    
    DISTANCE(53.8662,10.68117,53.8662,10.68
    ---------------------------------------
                                          0
    

    If you can't change the function then you will have to compare the values to decide whether it is safe to call it.