I noticed strange behaviour of NO_DATA_FOUND exception when thrown from function used in PLSQL.
Long story short - it does propagate from function when using assignment, and does not propagate (or is handled silently somewhere in between) when used in SELECT INTO.
So, given function test_me throwing NO_DATA_FOUND exception, when invoked as:
v_x := test_me(p_pk);
It throws an exception, while when invoked as:
SELECT test_me(p_pk) INTO v_x FROM dual;
it does not throw exception. This does not occur with other exceptions. Below You can find my test examples.
Could somebody please explain to me this behaviour?
set serveroutput on;
CREATE OR REPLACE FUNCTION test_me(p_pk NUMBER) RETURN NVARCHAR2
IS
v_ret NVARCHAR2(50 CHAR);
BEGIN
BEGIN
SELECT 'PYK' INTO v_ret FROM dual WHERE 1 = 1/p_pk;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line(chr(9)||chr(9)||chr(9)||' (test_me NO_DATA_FOUND handled and rerised)');
RAISE;
END;
RETURN v_ret;
END;
/
DECLARE
v_x NVARCHAR2(500 CHAR);
v_pk NUMBER;
PROCEDURE test_example(p_pk NUMBER)
IS
BEGIN
BEGIN
dbms_output.put_line(chr(9)||chr(9)||'Test case 1: Select into.');
SELECT test_me(p_pk) INTO v_x FROM dual;
dbms_output.put_line(chr(9)||chr(9)||'Success: '||NVL(v_x,'NULL RETURNED'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: NO_DATA_FOUND detected');
WHEN OTHERS THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: '||SQLCODE||' detected');
END;
dbms_output.put_line(' ');
BEGIN
dbms_output.put_line(chr(9)||chr(9)||'Test case 2: Assignment.');
v_x := test_me(p_pk);
dbms_output.put_line(chr(9)||chr(9)||'Success: '||NVL(v_x,'NULL RETURNED'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: NO_DATA_FOUND detected');
WHEN OTHERS THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: '||SQLCODE||' detected');
END;
END;
BEGIN
dbms_output.put_line('START');
dbms_output.put_line(' ');
dbms_output.put_line(chr(9)||'Example 1: Function throws some exception, both cases throws exception, everything is working as expected.');
test_example(0);
dbms_output.put_line(' ');
dbms_output.put_line(chr(9)||'Example 2: Query returns row, there is no exceptions, everything is working as expected.');
test_example(1);
dbms_output.put_line(' ');
dbms_output.put_line(chr(9)||'Example 3: Query inside function throws NO_DATA_FOUND, strange things happen - one case is throwing exception, the other is not.');
test_example(2);
dbms_output.put_line(' ');
dbms_output.put_line('END');
END;
/
DROP FUNCTION test_me;
A minimal example is:
CREATE FUNCTION raise_exception RETURN INT
IS
BEGIN
RAISE NO_DATA_FOUND;
END;
/
If you do:
SELECT raise_exception
FROM DUAL;
You will get a single row containing a NULL value - Ask Tom states:
it has ALWAYS been that way
and then followed up with:
no_data_found is not an error - it is an "exceptional condition". You, the programmer, decide if something is an error by catching the exceptional condition and handling it (making it be "not an error") or ignoring it (making it be an error).
in sql, no data found quite simply means "no data found", stop.
Under the covers, SQL is raising back to the client application "hey buddy -- no_data_found". The
client in this case says "ah hah, no data found means 'end of data'" and stops.
So the exception is raised in the function and the SQL client sees this and interprets this as there is no data which is a NULL value and "handles" the exception.
So
DECLARE
variable_name VARCHAR2(50);
BEGIN
SELECT raise_exception
INTO variable_name
FROM DUAL
END;
/
Will succeed as the DUAL table has a single row and the exception from the function will be handled (silently) and the variable will end up containing a NULL value.
However,
BEGIN
DBMS_OUTPUT.PUT_LINE( raise_exception );
END;
/
The exception is this time being passed from the function to a PL/SQL scope - which does not handle the error and passes the exception to the exception handler block (which does not exist) so then gets passed up to the application scope and terminates execution of the program.
And Ask Tom states:
Under the covers, PLSQL is raising back to the client application "hey -- no_data_found. The client in this case says "uh-oh, wasn't expecting that from PLSQL -- sql sure, but not PLSQL. Lets print out the text that goes with this exceptional condition and continue on"
You see -- it is all in the way the CLIENT interprets the ORA-xxxxx message. That message, when raised by SQL, is interpreted by the client as "you are done". That message, when raised by PLSQL and not handled by the PLSQL programmer, is on the other hand interpreted as "a bad thing just happened"
Both PLSQL and SQL actually do the same thing here. It is the CLIENT that is deciding to do something different.
Now, if we change the function to raise a different exception:
CREATE OR REPLACE FUNCTION raise_exception RETURN INT
IS
BEGIN
RAISE ZERO_DIVIDE;
END;
/
Then both:
SELECT raise_exception
FROM DUAL;
and:
BEGIN
DBMS_OUTPUT.PUT_LINE( raise_exception );
END;
/
do not know how to handle the exception and terminate with ORA-01476 divisor is equal to zero.
Related
I am writing a procedure to delete department from a table. It takes depatment id as argument and delete the department with given id. but it is not working correctly.When i didnot use EXCEPTION, it only give output when gien department id is present in table but if the id is not present in table it throw error. When i use exception, It did not check the if else condition.
Here is my procedure
CREATE OR REPLACE PROCEDURE del_job(j_id number) IS
jj_id bb_department.iddepartment%type;
BEGIN
SELECT IDDEPARTMENT
INTO jj_id
FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
jj_id := NULL;
IF (jj_id=j_id) THEN
DELETE FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
dbms_output.put_line ('Job Deleted');
ELSIF(jj_id=0) THEN
dbms_output.put_line ('No Job Deleted');
END IF;
END;
/
Your indentation seems to imply that you want your if statement to be part of the normal flow rather than part of the exception block. But your actual code has the if statement in the exception handler. Since you're assigning a null to jj_id in the exception handler before running the if statement and null is never equal to nor unequal to any value, neither your if nor your elsif clause can ever be true so neither dbms_output call will be made.
Assuming your indentation shows your actual intent, my guess is that you want a nested PL/SQL block for the select statement and exception handler.
CREATE OR REPLACE PROCEDURE del_job(j_id number) IS
jj_id bb_department.iddepartment%type;
BEGIN
BEGIN
SELECT IDDEPARTMENT
INTO jj_id
FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
jj_id := NULL;
END;
IF (jj_id=j_id) THEN
DELETE FROM BB_DEPARTMENT
WHERE IDDEPARTMENT=j_id;
dbms_output.put_line ('Job Deleted');
ELSIF(jj_id=0) THEN
dbms_output.put_line ('No Job Deleted');
END IF;
END;
/
You are making things overly difficult. DELETE sets sql%rowcount to the number of rows processed. So there is no need to select the department; just delete the appropriate id. If you want a confirmation message then test sql%rowcount. If the row was deleted it will contain 1 (or greater), if the id did not exist it will contain 0. Print the appropriate message.
create or replace procedure del_job(j_id number) is
begin
delete
from bb_department
where iddepartment=j_id;
if sql%rowcount > 0 then
dbms_output.put_line ('Job Deleted');
else
dbms_output.put_line ('No Job Deleted');
end if;
end del_job;
/
when we encounter an exception , the pointer will move to the exception part and if exception is handled will pointer come back or it move to the next part.?????
case 1: system defined exception
case 2: user defined exception
DECLARE
var_dividend NUMBER := 24;
var_divisor NUMBER := 0;
var_result NUMBER;
exception_div_zero EXCEPTION;
BEGIN
IF var_divisor = 0
THEN
RAISE exception_div_zero;
END IF;
var_result := var_dividend / var_divisor;
dbms_output.put_line(var_result);
EXCEPTION
WHEN exception_div_zero THEN
dbms_output.put_line(var_result);
END;
i want to know when the the exception encounter
after executing exception part will pointer come back to the next statement or it just exist the program.??enter code here
From the Oracle docs -
After an exception handler runs, control transfers to the next statement of the enclosing block. If there is no enclosing block, then:
If the exception handler is in a subprogram, then control returns to the invoker, at the statement after the invocation.
If the exception handler is in an anonymous block, then control transfers to the host environment (for example, SQL*Plus)
No, you don't go back to where you where. Rather the code keeps going after the exception handling.
Check this test out:
begin
dbms_output.put_line('One');
<<inner_one>>
begin
raise no_data_found;
dbms_output.put_line('Two');
exception
when no_data_found then
dbms_output.put_line('Three');
end inner_one;
dbms_output.put_line('Four');
raise no_data_found;
dbms_output.put_line('Five');
exception
when no_data_found then
dbms_output.put_line('Six');
end;
The output is:
One
Three
Four
Six
So Oracle's version of Try / Catch is to use anonymous blocks inside your blocks. You can nest blocks 255 times, which is a lot..
Example of using sub-block to parse a date string, if it fails, use current date/time.:
declare
l_date date;
begin
-- some code
-- parse date
begin
l_date := to_date(:input1, 'yyyy-mm-dd hh24:mi:ss');
exception
when others then
l_date := sysdate;
end;
--some more code
end;
Regards
Olafur
If I want to catch this error using exception handling, what are the things that I need to take care of?
wrong number or types of arguments in call (while calling a
procedure/function)
I trying was in different way. Could you please explain. I have a function:
create or replace function test5(v varchar2) return varchar as
begin
execute immediate 'begin sweet.g:=:v;end;'
using in v;
return sweet.g;
exception
when others then
return sqlcode||' '||sqlerrm;
end test5;
And a package spec and body:
create or replace package SWEET as
function c ( v varchar2,V2 VARCHAR2) return varchar2;
g varchar(100);
end;
/
create or replace package body SWEET as
function c(v varchar2, V2 varchar2) return varchar2 as
begin
return v||'hi'|| V2;
end c;
end;
/
when I execute the statement below, I was not able to catch 'wrong number or type of arguments'
select test5(sweet.c(,'hello')) from dual;
You should be able to get most of the answers in the PL/SQL manual, but when you are trying to trap an error that isn't one of the predefined ones you have to do something like:
DECLARE
deadlock_detected EXCEPTION;
PRAGMA EXCEPTION_INIT(deadlock_detected, -60);
BEGIN
... -- Some operation that causes an ORA-00060 error
EXCEPTION
WHEN deadlock_detected THEN
-- handle the error
END;
replacing -60 with your actual error, and deadlock_detected with whatever you wish to call it.
Let's say you have a procedure that takes in two numbers as arguments and outputs them:
create procedure testProc (p_param1 in number, p_param2 in number) is
begin
dbms_output.put_line('params: ' || p_param1 || ' ' || p_param2);
end;
If you execute this:
begin
testProc(13,188);
end;
You get output of: params: 13 188
If you do this:
begin
testProc(13);
exception when others then
dbms_output.put_line('SQLERRM: ' || SQLERRM);
end;
You get an error: PLS-00306: wrong number or types of arguments in call to 'TESTPROC'
To prevent this and catch the error, you can use dynamic SQL:
declare
v_sql varchar2(50);
v_result number;
begin
v_sql := 'begin testProc(13); end;';
execute immediate v_sql into v_result;
exception
when others then
dbms_output.put_line('SQLERRM: ' || SQLERRM);
end;
That will execute, and the error message will be displayed to dbms_output. In the when others then block you can write any logic you want for what should happen at that point.
I am new to PL/SQL and I am writing a somewhat complex script. In order to make the script a little cleaner, I would like to create many functions. However I am not very familiar with functions.
Can a function has VOID return type? If I have to have a return, how will it work with exception handling?
See below:
EX:
DECLARE
some_variable NUMBER;
FUNCTION myFunc1(pInput IN NUMBER) RETURN NUMBER
IS
BEGIN
-- DO SOMETHING
EXCEPTION
WHEN someException THEN
DBMS_OUTPUT.PUT_LINE('ERROR someException ');
WHEN Others THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
-----------------------------
-- WHERE DO I PUT THE RETURN?
-----------------------------
END;
FUNCTION myFunc2(pInput IN NUMBER) -- CAN IT RETURN NOTH
IS
fun1Return NUMBER;
BEGIN
-- DO SOMETHING
fun1Return := myFunc1(1);
EXCEPTION
WHEN someException THEN
DBMS_OUTPUT.PUT_LINE('ERROR someException ');
WHEN Others THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
END;
BEGIN
--- DO SOMETHING
some_variable := myFunc2(2);
EXCEPTION
WHEN someException THEN
DBMS_OUTPUT.PUT_LINE('ERROR someException ');
WHEN Others THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
END;
This is covered pretty well in the documentation.
A function has to return before the exception handler, if you have one. But in your example you should not be catching the exceptions really as you are just squashing the errors, and you're assuming whoever calls this will be able to see the dbms_output which is not a safe assumption. If you don't re-raise the (or any) exception then you still need to return from the exception handlers as well as from the main body of the block:
FUNCTION myFunc1(pInput IN NUMBER) RETURN NUMBER
IS
BEGIN
-- DO SOMETHING
RETURN 0;
EXCEPTION
WHEN someException THEN
DBMS_OUTPUT.PUT_LINE('ERROR ' || SQLERRM);
-- how do you know where the exception was raised?
RETURN -1;
WHEN Others THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
-- how does the caller know what went wrong?
RETURN -2;
END;
You're also hiding all details of the error and removing any hope of being able to find out what actually went wrong. Catching when others is particularly evil, especially if you don't re-raise the exception. If you really want to display your own message you should still re-raise the original exception, in which case you won't need to return again:
FUNCTION myFunc1(pInput IN NUMBER) RETURN NUMBER
IS
BEGIN
-- DO SOMETHING
RETURN 0;
EXCEPTION
WHEN someException THEN
DBMS_OUTPUT.PUT_LINE('ERROR ' || SQLERRM);
RAISE;
WHEN Others THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
RAISE;
END;
Which is very slightly better but you still lose some information about the error stack. You probably don't really want to catch these at all:
FUNCTION myFunc1(pInput IN NUMBER) RETURN NUMBER
IS
BEGIN
-- DO SOMETHING
RETURN 0;
END;
The only reason to catch an exception really is if it's something you sort of expect and can handle elegantly. Anything else - especially other - is almost always better off left to propagate up the call stack.
You can also have multiple returns if you have branches in your function.
FUNCTION myFunc1(pInput IN NUMBER) RETURN NUMBER
IS
BEGIN
IF SOMETHING THEN
RETURN 1;
END IF;
RETURN 0;
END;
As #couling says, a function with no return is a procedure. Stripping the exception again:
PROCEDURE myProc2(pInput IN NUMBER)
IS
fun1Return NUMBER;
BEGIN
-- DO SOMETHING
fun1Return := myFunc1(1);
END;
So the problem i am having is that if i execute the following procedure and the cursor doesnt find the parameter being passed, it continues to execute the block (insert statement) but instead of throwing the NO_DATA_FOUND exception error it throws a parent/foreign key error.
CREATE OR REPLACE PACKAGE ASSIGNMENT3 IS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE);
END ASSIGNMENT3;
/
CREATE OR REPLACE PACKAGE BODY ASSIGNMENT3 AS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
CURSOR ADCOST_CUR IS
SELECT ACTUALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
V_TOTALCOST NUMBER;
BEGIN
V_TOTALCOST := 0;
FOR INVOICE_REC IN ADCOST_CUR
LOOP
V_TOTALCOST := V_TOTALCOST + INVOICE_REC.ACTUALCOST;
END LOOP;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ERROR:The campaign title you entered returned no record(s), please enter a valid campaign title.');
COMMIT;
END END_CAMPAIGN;
END ASSIGNMENT3;
/
SET SERVEROUTPUT ON
EXECUTE ASSIGNMENT3.END_CAMPAIGN('Panasonic 3D TV');
While the parent foreign key error is correct, i dont want the block to execeute if the cursor doesnt return a row. Why is this happening?
Also, in terms of placing the COMMIT, where exactly do i tell it to COMMIT? Before the exception or after?
This is for a uni assignment.
When you loop over a cursor like that, if the cursor finds no matching rows, the loop simply doesn't execute at all. A NO_DATA_FOUND exception would only be raised if you had a SELECT ... INTO ... statement inside the BEGIN/END block that did not return any rows.
Where you have the COMMIT placed now, it is part of the EXCEPTION block -- but your indentation implies that you want it to execute whether the exception occurred or not. In this case, I would just put the COMMIT immediately after the INSERT, since it only matters if the INSERT is successful.
"So is there no way to have the NODATAFOUND exception trigger when
using a cursor, if the CTITLE parameter isnt found in the table"
What you could do is test the value of V_TOTAL_COST. If it is zero raise an exception, like this:
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
CURSOR ADCOST_CUR IS
SELECT ACTUALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
V_TOTALCOST NUMBER;
BEGIN
V_TOTALCOST := 0;
FOR INVOICE_REC IN ADCOST_CUR
LOOP
V_TOTALCOST := V_TOTALCOST + INVOICE_REC.ACTUALCOST;
END LOOP;
if v_total_cost = 0 then
raise no_data_found;
end if;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
COMMIT;
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('ERROR:The campaign title you entered returned no record(s), please enter a valid campaign title.');
END END_CAMPAIGN;
This assumes you have a business rule that ACTUAL_COST cannot be zero.
Alternatively, there is the clunkier workaround of incrementing a counter in the loop and testing whether it is zero after the loop.
As for where to place the commit I would say the answer is not inside the procedure. The client (sqlplus in this case) should determine if the transaction will commit or rollback as the call to end the campaign may be just a part of a wider process. Also assuming that a campaign can exist without any advertisements then I would have an explicit check that the campaign title is valid perhaps against the table of CAMPAIGN? as suggested below:
CREATE OR REPLACE PACKAGE ASSIGNMENT3 IS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE);
END ASSIGNMENT3;
/
CREATE OR REPLACE PACKAGE BODY ASSIGNMENT3 AS
PROCEDURE END_CAMPAIGN(CTITLE IN CAMPAIGN.CAMPAIGNTITLE%TYPE) IS
V_VALID_CAMPAIGN INTEGER;
V_TOTALCOST NUMBER;
BEGIN
-- Check this campaign title is valid
/* Will get you NO_DATA_FOUND here if CTITLE is invalid so wrap in
another BEGIN END block to throw own custom error that the client
of this procedure can handle (if it wants) */
BEGIN
SELECT 1
INTO V_VALID_CAMPAIGN
FROM CAMPAIGN
WHERE CAMPAIGNTITLE = CTITLE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20000,'The campaign title you entered returned no record(s), please enter a valid campaign title.');
END;
-- Now tot up the cost of ads in this campaign and raise the invoice
SELECT SUM(ACTUALCOST)
INTO V_TOTALCOST
FROM ADVERTISEMENT
WHERE ADVERTISEMENT.CAMPAIGNTITLE = CTITLE;
INSERT INTO INVOICE(INVOICENO, CAMPAIGNTITLE, DATEISSUED, DATEPAID, BALANCEOWING, STATUS)
VALUES (AUTOINCREMENTINVOICE.nextval, CTITLE, SYSDATE, NULL,V_TOTALCOST,NULL);
END END_CAMPAIGN;
END ASSIGNMENT3;
/
EXECUTE ASSIGNMENT3.END_CAMPAIGN('Panasonic 3D TV');
COMMIT;