SQL: exception handling in a function - oracle

I have encountered a rather mysterious problem today. As I executed my SQL function f_interestrate()( which should raise a from me defined exception when one of the parameters is equal to 0 ) with the following parameters:
SELECT GENERAL_FUNCTIONS.F_INTERESTRATE(2500000, 0.10, 0) FROM dual;
Gave me the following error:
ORA-06503: PL/SQL: Function returned without value
ORA-06512: at "NOAHBASE.GENERAL_FUNCTIONS", line 73
06503. 00000 - "PL/SQL: Function returned without value"
*Cause: A call to PL/SQL function completed, but no RETURN statement was executed.
*Action: Rewrite PL/SQL function, making sure that it always returns
a value of a proper type.
But as you may see in the following code sample the function should instead raise the form me defined exception ex_invalid_devisor. Not forget to mention that this function is nested inside a package.
FUNCTION f_interestrate(pn_principal NUMBER, pn_interest NUMBER, pn_years NUMBER) RETURN NUMBER IS
vn_interestrate NUMBER;
ex_invalid_devisor EXCEPTION;
BEGIN
IF pn_principal = 0 OR
pn_interest = 0 OR
pn_years = 0 THEN
RAISE ex_invalid_devisor;
ELSE
vn_interestrate := ((pn_interest/pn_principal)-1)/pn_years;
RETURN vn_interestrate;
END IF;
EXCEPTION
WHEN ex_invalid_devisor THEN
DBMS_OUTPUT.PUT_LINE('Devisor must be bigger then 0');
END;
Am I doing anything wrong?

This is how you should handle ... I added 'return -1' in your code. HTH.
create or replace FUNCTION f_interestrate(pn_principal NUMBER, pn_interest NUMBER, pn_years NUMBER) RETURN NUMBER IS
vn_interestrate NUMBER;
ex_invalid_devisor EXCEPTION;
BEGIN
IF pn_principal = 0 OR
pn_interest = 0 OR
pn_years = 0 THEN
RAISE ex_invalid_devisor;
ELSE
vn_interestrate := ((pn_interest/pn_principal)-1)/pn_years;
RETURN vn_interestrate;
END IF;
EXCEPTION
WHEN ex_invalid_devisor THEN
DBMS_OUTPUT.PUT_LINE('Devisor must be bigger then 0');
return -1;
END;
SQL> select F_INTERESTRATE(2500000, 0.10, 0) FROM dual;
F_INTERESTRATE(2500000,0.10,0)
------------------------------
-1

As PL/SQL already has a perfectly good zero_divide exception, I'd be tempted to just write the function as:
create or replace function f_interestrate
( pn_principal number
, pn_interest number
, pn_years number )
return number
as
begin
return ((pn_interest / pn_principal) - 1) / pn_years;
end;
then you'll get the default failure message:
SQL> select f_interestrate(2500000, 0.10, 0) from dual;
select f_interestrate(2500000, 0.10, 0) from dual
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at "XXX.F_INTERESTRATE", line 8
Or if you really need some customised handling,
create or replace function f_interestrate
( pn_principal number
, pn_interest number
, pn_years number )
return number
as
begin
return ((pn_interest / pn_principal) - 1) / pn_years;
exception
when zero_divide then
[[[ do something here ]]]
end;
I notice you also raise your custom ex_invalid_devisor exception when pn_interest is zero, even though this isn't used as a divisor, so perhaps there is some subtle logic I am missing here.
(Edit: thinking about it, if pn_interest is zero then maybe you just need to return pn_principal regardless.)

Related

Handling exception 1) when defining Oracle function 2) When Calling Oracle function

My Oracle function below(code1) have no exception handling.
Therefore if it is called(code2) with 0, error shows.
--Code 1
CREATE OR REPLACE FUNCTION TEST2
(P1 IN VARCHAR2)
RETURN NUMBER AS V_VALUE NUMBER;
BEGIN
SELECT(
SELECT 1/TO_NUMBER(P1)
FROM DUAL
)
INTO V_VALUE
FROM DUAL;
RETURN V_VALUE;
END;
/
--Code2
SELECT TEST2('0') FROM DUAL;
Please, help to add exception handling for each 1) 2) case as below.
case 1) when defining function, how to modify code1
for function to return -1 if a system exception,
including dividing by zero happen?
case 2) Without adding exception in my Oracle funtion,
how to modify code2 for the reslut to be -1 if a system excetion, including dividing zero happens in the function?
I think you are making this more complicated than it needs to be. I did not compile this code below, but should provide as an example. Regarding exceptions, it is OK to handle divide-by-zero, but hiding all other exception types is very, very bad design. Also, if I pass in test2(-1), then the result will be a valid value of -1. Are you assured your input parameter is always positive. Regardless, here is a solution which checks for a 0 parameter, and avoids the division problem. A better solution is to define TEST1 P1 as a NUMBER to begin with, and let the caller format it as needed. If not, I could pass in something like TEST2('fsfd') and get an exception.
CREATE OR REPLACE FUNCTION TEST2(P1
IN VARCHAR2)
RETURN NUMBER;
D_Result NUMBER : = -1;
BEGIN
IF P1 <> 0 THEN
D_result := 1/TO_NUMBER(P1);
END IF;
RETURN D_Result;
END
If you really want to throw a divide error, you can catch is like this:
DECLARE
result NUMBER;
BEGIN
result := test2(0);
EXCEPTION
WHEN OTHERS THEN
result := -1;
END;
#OldProgrammer shows how to prevent the exception from occurring, which is the best choice. However, if you want to allow the exception to occur and catch it in the function you could use:
CREATE OR REPLACE FUNCTION TEST2(P1 IN VARCHAR2)
RETURN NUMBER
AS
V_VALUE NUMBER;
BEGIN
BEGIN
V_VALUE := 1 / TO_NUMBER(P1);
EXCEPTION
WHEN OTHERS THEN
V_VALUE := -1;
END;
RETURN V_VALUE;
END TEST2;
or you could simplify this to
CREATE OR REPLACE FUNCTION TEST2(P1 IN VARCHAR2)
RETURN NUMBER
AS
BEGIN
RETURN 1 / TO_NUMBER(P1);
EXCEPTION
WHEN OTHERS THEN
RETURN -1;
END TEST2;

Comparing number with varchar2

I have this function and I need to compare number with varchar.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
begin
declare odd integer;
declare i_perecentage=0;
begin
if i_odd ='SP'
then
return (0);
end if;
odd:=round(to_number((1-i_perecentage/100)*i_odd),2);
if odd<1
then
return(i_odd);
else
return(round(odd,2));
end if;
end;
end;
/
PS: I edited function and i resolve problem with comparing , now i have another situation that i dont like..
This function returns calculated percentage of i_odd. The problem is that if i pass 0 in i_percentage in results i get result with no decimal places(for example: i_odd = 3.10 and i_percentage = 0 i get odd = 3 but if I pass i_odd = 3.10 and i_percentage = 1 i get odd = 3.10 ).
Why is on i_percentage = 0 i dont get decimal places ??
If you want to validate a varchar2 field as a number in PL/SQL, typically you'd just try converting it to a number and catch the exception.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
BEGIN
-- if i_odd = 'SP' (or another non-number), this will throw an ORA-01722
-- exception which will be caught in the exception block, below
odd := to_number(i_odd); -- you might want a format mask here
--... now you can use "odd" as a number
EXCEPTION WHEN INVALID_NUMBER THEN
return 0;
END;
/
You can also nest a begin..end block in the middle of your code just to catch exceptions, if that works better for you:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
begin
begin
odd := to_number(i_odd); -- you might want a format mask here
exception when INVALID_NUMBER then
odd := 0;
end;
--... now you can use "odd" as a number
end;
/
The reason why you can't catch the invalid_number exception is because you are declaring the input parameter as a number. When you call your function, Oracle tries to convert the string to a number first (and it fails of course, before entering your code at all).
If you change the input parameter to varchar2, then the conversions to number (implicit in this case) is done inside the function, and invalid numbers can be caught and handled as you want (here I'm just returning a different string to denote the issue):
create or replace function is_odd_even(i_num in varchar2)
return varchar2
is
begin
-- conversion to number is done here
if (mod(i_num, 2) = 0) then
return 'EVEN';
else
return 'ODD';
end if;
exception
when INVALID_NUMBER or VALUE_ERROR then
-- do something meaningful
return 'INV';
end;
Usage example:
with x as (
select '1' as val from dual
union all
select 'SP' as val from dual
union all
select '2' as val from dual
)
select x.val, is_odd_even(x.val)
from x;
Output:
1 ODD
SP INV
2 EVEN
SOLUTION:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return varchar2 as
odd varchar2(10);
ret_value number(4);
begin
if (i_odd ='SP') or i_odd is null then
return 'SP';
else
odd :=ROUND( TO_NUMBER( ( 1 - (play_beting.get_odds_percentage(i_id) / 100 ) ) * TO_NUMBER(i_odd) ), 2);
IF(odd < 1) THEN
ret_value := TO_NUMBER(i_odd);
ELSE
ret_value := to_char(odd,'9999.00');
END IF;
END IF;
RETURN to_char(ret_value,'9999.00');
END getOdds;

PL/SQL exception handling in function

I am trying to generate DIVIDE_BY_ZERO exception in my oracle PL/SQL program. I using function but when I run my program I am getting error which is shown below. Can anyone tell me what is wrong in my program ?
code:
CREATE OR REPLACE
PACKAGE CALCULATOR AS
FUNCTION AddNumber(addend IN Number, Addend2 IN Number) RETURN NUMBER;
FUNCTION DivNumber(divend IN Number, divend2 IN Number) RETURN NUMBER;
END CALCULATOR;
/
CREATE OR REPLACE
PACKAGE BODY CALCULATOR AS
FUNCTION AddNumber(addend IN Number, Addend2 IN Number) RETURN NUMBER AS
BEGIN
return addend + addend2;
END AddNumber;
FUNCTION DivNumber(divend IN Number, divend2 IN Number) RETURN NUMBER AS
BEGIN
return divend / divend2;
EXCEPTION
WHEN ZERO_DIVIDE THEN
NULL;
END DivNumber;
END CALCULATOR;
/
select calculator.AddNumber(3,4) from dual;
select calculator.DivNumber(12,0) from dual;
output:
you can deal with exceptions like this
FUNCTION DivNumber(divend IN Number, divend2 IN Number) RETURN NUMBER AS
e_ZERO_DIVIDE EXCEPTION;
BEGIN
-- the condition
IF divend2 = 0 THEN
Raise e_ZERO_DIVIDE;
END IF;
return divend / divend2;
--Exception handling
EXCEPTION
WHEN e_ZERO_DIVIDE THEN
dbms_output.put_line('Division by 0 or null');
RETURN 1; -- or 0 or null
WHEN OTHERS THEN
dbms_output.put_line('ERROR: '||sqlerrm);
RETURN 1; -- or 0 or null
END DivNumber;
IMO you should never write code which throws an avoidable exception. There may be times when you can't avoid raising an exception but in this case you can. I suggest that you rewrite your function as follows:
FUNCTION DivNumber(numerator IN Number, denominator IN Number) RETURN NUMBER AS
BEGIN
IF NVL(denominator, 0) <> 0 THEN
return numerator / denominator;
ELSE
RETURN NULL;
END IF;
END DivNumber;
Best of luck.

ORA-06575: is in an invalid state

Hello I am attempting to do the following problem and keep receiving errors. Create a user-defined function called DOLLARS_BY_VEHICLE_TYPE that receives an input parameter of a concatenated make and model and then queries the VEHICLES and SALES_FACTS tables to return the total gross sales amount of the sales by that combination. My code is as follows:
CREATE OR REPLACE FUNCTION DOLLARS_BY_VEHICLE_TYPE (V_AUTODESC VARCHAR2)
RETURN VARCHAR2
IS
V_AMT VARCHAR2 (50);
BEGIN
SELECT NVL (SUM (GROSS_SALES_AMOUNT), 0)
INTO V_AMT
FROM VEHICLES v, SALES_FACTS s
WHERE V.Vehicle_Code = S.Vehicle_Code AND V.DESCRIPTION= V_AUTODESC;
RETURN V_AMT;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
/
However, when I run it I get:
Warning: Function created with compilation errors.
ERROR at line 1:
ORA-06575: Package or function DOLLARS_BY_VEHICLE_TYPE is in an invalid state
Please see that you are trying to return the sum from your function and that can be only number/int/float. So the return type of your function should be number not varchar2. Also note that you are using a deprecated sql syntax which can be changed. See below:
CREATE OR REPLACE FUNCTION DOLLARS_BY_VEHICLE_TYPE (V_AUTODESC VARCHAR2)
-- RETURN VARCHAR2
RETURN NUMBER
IS
--V_AMT VARCHAR2 (50);
V_AMT NUMBER;
BEGIN
SELECT NVL (SUM (GROSS_SALES_AMOUNT), 0)
INTO V_AMT
FROM VEHICLES v
inner join SALES_FACTS s
ON V.Vehicle_Code = S.Vehicle_Code
where V.DESCRIPTION = V_AUTODESC;
RETURN V_AMT;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
/

Testing exception handling in Oracle 11g

I have this oracle code
FUNCTION get_enc_val(p_in IN VARCHAR2,
p_key IN VARCHAR2
)
RETURN raw
IS
p_title_procedure_name VARCHAR2(100) := 'get_enc_val';
l_enc_val RAW(2000);
l_mod PLS_INTEGER := DBMS_CRYPTO.ENCRYPT_AES128 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5;
v_key VARCHAR2(16);
encryption_ex Exception;
BEGIN
v_key := RPAD(SUBSTR(TRIM(p_key), 1, 16), 16, '0');
l_enc_val := DBMS_CRYPTO.encrypt(UTL_RAW.cast_to_raw(p_in), l_mod, UTL_RAW.cast_to_raw(v_key));
RAISE encryption_ex;
RETURN l_enc_val;
EXCEPTION
WHEN OTHERS THEN
service_func.log_error(p_title_package_name || '.' || p_title_procedure_name, 'Proc', NULL, SYSDATE, SQLERRM, p_in || '~' || p_key);
RETURN 'Encryption_ERROR';
END;
When I run this I get ORA-06510 Unhandled user-defined exception while it should really return the string 'Encryption_ERROR'.What gives? It goes to the Exception block, because I see the result of log_error function. Question is, isn't the Exception block supposed to HANDLE ANY exception?
I am a bit confused.
The problem is that your second RETURN statement is returning a VARCHAR2 while your function is declared to return a RAW. You could fix that by calling UTL_RAW.CAST_TO_RAW, i.e.
EXCEPTION
WHEN OTHERS THEN
service_func.log_error(p_title_package_name || '.' ||
p_title_procedure_name,
'Proc',
NULL,
SYSDATE,
SQLERRM,
p_in || '~' || p_key);
RETURN utl_raw.cast_to_raw( 'Encryption_ERROR' );
END;
If I declare two functions, one that returns a hard-coded string and one that returns a RAW, you'll see the difference (I'm removing the DBMS_CRYPTO calls and the LOG_ERROR call). If I declare a function that returns a RAW, you get a result back (though a human would have to convert the raw back into a string to make sense of the result)
SQL> ed
Wrote file afiedt.buf
1 create or replace function throw_exception
2 return raw
3 is
4 my_exception exception;
5 begin
6 raise my_exception;
7 exception
8 when others then
9 return utl_raw.cast_to_raw( 'Foo' );
10* end;
SQL> /
Function created.
SQL> select throw_exception
2 from dual;
THROW_EXCEPTION
-----------------------------------------------------------------------------
466F6F
If I just return a string, I'll get the same exception you were getting
SQL> ed
Wrote file afiedt.buf
1 create or replace function throw_exception2
2 return raw
3 is
4 my_exception exception;
5 begin
6 raise my_exception;
7 exception
8 when others then
9 return 'Foo';
10* end;
SQL> /
Function created.
SQL> select throw_exception2
2 from dual;
select throw_exception2
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: hex to raw conversion error
ORA-06512: at "SCOTT.THROW_EXCEPTION2", line 9
ORA-06510: PL/SQL: unhandled user-defined exception
Of course, the other option would be to declare that the function returns a VARCHAR2. But I would much rather have hashes and encrypted data in a RAW than a VARCHAR2 so that you never have to worry about things like character set conversion issues mangling the data.
Since you are sure that service_func.log_error(...); is not raising the error, the error must be thrown by the calling procedure/function.
And here's something that raises the same error.
create or replace function abc return raw is
begin
return 'Encryption_ERROR';
end;
/
declare
r raw(50);
begin
r := abc;
end;
/

Resources