<<SQL_ERROR>> block - oracle

In our Stored procedures we have the following code towards the very end.
<<SQL_ERROR>>
V_SYS_ERROR_MSG := SUBSTR(SQLERRM, 1, 252);
DBMS_OUTPUT.PUT_LINE('ERROR IN EXECUTION IN PROCEDURE');
DBMS_OUTPUT.PUT_LINE('THE ERROR CODE IS ' || V_SYS_ERROR || '- ' ||
V_SYS_ERROR_MSG);
we have statements like following which call the error block.
IF V_SYS_ERROR <> 0 THEN
GOTO SQL_ERROR;
the DBMS output statements come even when there is no error. How can we avoid this?

I am not recommending this GOTO approach: as others have already said, exceptions are the correct way to handle errors in PL/SQL. But to address your specific question, you could do this:
BEGIN
IF V_SYS_ERROR <> 0 THEN
GOTO SQL_ERROR;
END IF;
GOTO PROC_END;
<<SQL_ERROR>>
V_SYS_ERROR_MSG := SUBSTR(SQLERRM, 1, 252);
DBMS_OUTPUT.PUT_LINE('ERROR IN EXECUTION IN PROCEDURE');
DBMS_OUTPUT.PUT_LINE('THE ERROR CODE IS ' || V_SYS_ERROR || '- ' ||
V_SYS_ERROR_MSG);
<<PROC_END>>
NULL;
END;
Of course, this still involves changing the code, so if you are doing that why not do it properly anyway? i.e.
DECLARE
SQL_ERROR EXCEPTION;
BEGIN
IF V_SYS_ERROR <> 0 THEN
RAISE SQL_ERROR;
END IF;
EXCEPTION
WHEN SQL_ERROR THEN
V_SYS_ERROR_MSG := SUBSTR(SQLERRM, 1, 252);
DBMS_OUTPUT.PUT_LINE('ERROR IN EXECUTION IN PROCEDURE');
DBMS_OUTPUT.PUT_LINE('THE ERROR CODE IS ' || V_SYS_ERROR || '- ' ||
V_SYS_ERROR_MSG);
RAISE;
END;
By the way, DBMS_OUTPUT.PUT_LINE is not suitable for output of error messages in a production application system. Only use it for debugging during development.

you should avoid GOTO statements, they are messy as you have noticed. PL/SQL comes with error handling, you should use the EXCEPTION synthax to deal with errors:
BEGIN
<code goes here>
EXCEPTION
WHEN <exception> THEN
<deal_with_it>
WHEN OTHERS THEN
<log_error>
RAISE;
END;

I don't think you'll get the results you want without using an exception handler. From the PL/SQL User's Guide:
SQLERRM with no argument is useful
only in an exception handler. Outside
a handler, SQLERRM with no argument
always returns the normal, successful
completion message.
So first of all, it is possible that an error is happening but you are seeing a message saying "normal, successful completion" because that is what SQLERRM will always return in this context.
Assuming that's not the case, it sounds like you are simply allowing control to flow from the "normal" code into the "error handler". If the error handler is at the very end of the procedure, then a simple fix would be to add a RETURN statement just before the <> label.
A better fix would be to move the error-handler code to a separate procedure and call that procedure instead of using a GOTO.

Related

How to handle different exceptions in PL/SQL differently?

My stored-procedure is looping executing different statements. I want to handle the following situations:
When the statement returns nothing (no_data_found), I want to quietly skip the rest of the loop (continue).
When the statement causes an error of any type, I want to report it, and then skip the rest of the loop (continue);
When the statement finds rows, I want to report it.
The code looks like:
...
LOOP
stmt := 'select * ......';
BEGIN
EXECUTE IMMEDIATE stmt;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
WHEN OTHERS THEN
dbms_out.put_line(stmt || ': ' || SQLCODE);
CONTINUE;
END;
dbms_out.put_line('Found! Use: ' || stmt);
END LOOP;
The above elicits no errors, but the Found-line is printed for every loop-iteration, including for statements, that yield no results...
Why is the CONTINUE-directive ignored -- am I wrong expecting the directive to be obeyed for any exception -- be it NO_DATA_FOUND or anything else?
In your exception block, the action for your NO_DATA_FOUND handler is NULL - so it executes the NULL statement (i.e. does nothing) and falls out of the BEGIN-END block, hitting the dbms_out.put_line('Found! Use: ' || stmt); statement. The only handler which will execute CONTINUE; is the WHEN OTHERS.
One way to get the behavior you describe is to do a SELECT COUNT(*)... into a numeric variable and then just check to see how many rows are returned:
DECLARE
csr SYS_REFCURSOR;
nCount NUMBER;
BEGIN
LOOP
stmt := 'SELECT COUNT(*) FROM (SELECT * from ... WHERE ...)';
OPEN csr FOR stmt;
FETCH csr INTO nCount;
CLOSE csr;
IF nCount > 0 THEN
dbms_out.put_line('Found! Use: ' || stmt);
ELSE
dbms_out.put_line(stmt || ': ' || SQLCODE);
END IF;
END LOOP;
END;
Of course this is not really valid as there's no way for the value of stmt to change, but I suspect your "real" code handles that.

Shared code for multiple specific exceptions in PL/SQL

I am fairly new to PL/SQL and am trying to implement some exception handling for a package I have written.
I have come across a situation where I have multiple exceptions that may be raised.
In the event these exceptions are raised I may want to do something specific for each of the exceptions, for example if exception A is raised then close and delete a file, but if exception B is raised then I need only close a cursor and send an email to warn someone that a fatal error has occurred.
This is fine and I understand how to use WHEN <EXCEPTION_NAME> THEN.
The problem I am having is that I can't write generic code which occurs for any exception raised to do something I will always want done in the event of an exception, for example writing to the log file. This means that I have to duplicate lines of code for each exception type as shown below.
DECLARE
test_exception EXCEPTION;
BEGIN
--some code
RAISE test_exception;
--some code
EXCEPTION
WHEN test_exception THEN
SEND_EMAIL('Something went wrong');
WRITE_TO_LOG('Error ' || SQLCODE || ' | ' || SUBSTR(SQLERRM, 1, 200) || ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
WHEN OTHERS THEN
SOME_OTHER_FUNCTION();
WRITE_TO_LOG('Error ' || SQLCODE || ' | ' || SUBSTR(SQLERRM, 1, 200) || ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
What I would like to achieve is something similar to this, which does not compile.
DECLARE
test_exception EXCEPTION;
BEGIN
--some code
RAISE test_exception;
--some code
EXCEPTION
WRITE_TO_LOG('Error ' || SQLCODE || ' | ' || SUBSTR(SQLERRM, 1, 200) || ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
WHEN test_exception THEN
SEND_EMAIL('Something went wrong');
WHEN OTHERS THEN
SOME_OTHER_FUNCTION();
END;
Obviously this example isn't complete but gives a rough idea. For a single duplicated line like this it isn't a problem, but if there are many cursors or files to close or other housekeeping this seems a bit verbose/tedious.
Is there a WHEN ALL THEN clause or similar? Would this be a use case for some kind of GOTO? Or am I trying to apply the wrong sort of concept here.
I can't seem to find people asking the same question which to me means I am approaching the issue in the wrong way or I am missing some basic knowledge.
Thanks
PL/SQL will only execute one exception block. So if you have some generic code (e.g. logging) you want to run for every exception, you have to it do in when others.
Then check the sqlcode to do exception specific handling. For example:
begin
raise TOO_MANY_ROWS;
exception
when others then
dbms_output.put_line ( 'Generic stuff' );
case sqlcode
when -1422 then
dbms_output.put_line ( 'TMR specific' );
when -1476 then
dbms_output.put_line ( 'ZD specific' );
else
null;
end case;
raise;
end;
/
Generic stuff
TMR specific
ORA-01422: exact fetch returns more than requested number of rows
begin
raise ZERO_DIVIDE;
exception
when others then
dbms_output.put_line ( 'Generic stuff' );
case sqlcode
when -1422 then
dbms_output.put_line ( 'TMR specific' );
when -1476 then
dbms_output.put_line ( 'ZD specific' );
else
null;
end case;
raise;
end;
/
Generic stuff
ZD specific
ORA-01476: divisor is equal to zero
Now, whether this is a good idea is debatable.
when others exception blocks should re-raise the exception in some way. This to prevent you from suppressing serious unexpected errors (like out of disk space). But for some specific exceptions you may want to carry on processing.
Addendum
If you have user-defined exceptions, you need to do a bit of extra work if you wand to handle these. One way to do it is to create a standard exceptions package. In it, name, initialize, and define value constants for all the exceptions you'll use.
You can then use these definitions for user-defined exceptions:
create or replace package excepts as
user_defined exception ;
user_defined_val pls_integer := -20001;
pragma exception_init ( user_defined, -20001 );
end;
/
begin
raise excepts.USER_DEFINED;
exception
when others then
dbms_output.put_line ( 'Generic stuff' );
case sqlcode
when -1422 then
dbms_output.put_line ( 'TMR specific' );
when -1476 then
dbms_output.put_line ( 'ZD specific' );
when excepts.user_defined_val then
dbms_output.put_line ( 'User-defined specific' );
else
null;
end case;
raise;
end;
/
Generic stuff
User-defined specific
ORA-20001:
If I'am undestanding correctly - solution is very simple. When you raising an exception, you can handle it in nested block and pass to the outer block by RAISE
DECLARE
test_exception EXCEPTION;
begin
RAISE test_exception;
--some code
EXCEPTION
WHEN OTHERS THEN
BEGIN
dbms_output.put_line( 'WRITE_TO_LOG');
RAISE;
EXCEPTION
WHEN test_exception THEN
dbms_output.put_line( 'test_exception');
WHEN OTHERS THEN
dbms_output.put_line( 'some other function');
end;
END;
output:
WRITE_TO_LOG
send mail

How can I modify my PL/SQL procedure to go to my exception handling?

I am using SQL developer to write a procedure.
The objective is to get the name of the drainage system from one table and get the count of how much the drainage system name code appears in another table.
My procedure works, but when I enter an incorrect value, it does not go to the exception section. For example, when I input ‘Mexico River’, this name does not exist in the table. So, I want my exception section to realize that this is an incorrect value being entered.
My question is how do I modify my code, so it can detect incorrect values and go to my exception section.
Below is a snippet of my code:
PROCEDURE number_of_rivers --second procedure with 1 parameter
(input_sysName IN TBLDrainage.DRAINAGE_SYS%TYPE)
is
-- Create a cursor
cursor c_river is
select code, drainage_sys
from TBLDRAINAGE
where DRAINAGE_SYS = input_sysName;
v_rivercount Number;
r_variable c_river %rowtype;
Begin
FOR r_variable in c_river
loop
select count (Drainage_sys) into v_rivercount
from TBLRIVER
where DRAINAGE_SYS = r_variable.code;
DBMS_OUTPUT.PUT_LINE (UPPER(input_sysName) || ' has ' || v_rivercount || ' river(s) in the drainage system.');
end loop;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Error: Please enter a valid drainage system name');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error in finding system');
END ;
The CURSOR..FOR loop has the property of executing zero or more times. It doesn't throw NO_DATA_FOUND.
There are a couple of solutions. One is to include a count inside the loop, and raise an exception afterwards.
l_count := 0;
FOR r_variable in c_river
loop
....
l_count := l_count + 1;
end loop;
if l_count = 0 then
raise NO_DATA_FOUND;
end if;
The other would be to validate the input parameter at the start of your program.
begin
open c_river;
fetch c_river into r_variable;
if c_river%notfound then
raise NO_DATA_FOUND;
else
select count (Drainage_sys)
into v_rivercount
from TBLRIVER
where DRAINAGE_SYS = r_variable.code;
DBMS_OUTPUT.PUT_LINE (UPPER(input_sysName) || ' has ' || v_rivercount || ' river(s) in the drainage system.');
end if;
close c_river;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Error: Please enter a valid drainage system name');
close c_river;
END ;
In this solution I have removed the loop, because I would expect a look-up on drainage system should be unique and return one record. Please re-instate the loop if your data model isn't like that.
I have retained your names for the cursor and its row variables but you should re-name them. They are used for selecting drainage systems not rivers, and their names ought to reflect that. Discipline in naming things is a useful habit to acquire, as misleading variable names will cause confusion in larger chunks of code.
Also, swallowing exceptions like this is very bad:
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error in finding system');
Oracle has thousands of error messages: it better to do nothing with the error message than to throw it away.

Exit execution when error occurs PL/SQL

I would like to know, how can I exit the execution when an error occurs. In Microsoft SQL Server there is a RETURN clause, which does the work. But I would like to know similar functionality in Oracle. I am using Oracle Sql Developer. Here is the script I am using:
First block throws error due to Unique Key Violation, even though it throws error the execution goes to next block and executes the insert statement. I want to end the execution or exit at first block of code itself.
Please help me to write the code.
First anonymous PL/SQL block:
set serveroutput on;
BEGIN
insert into test values(1);
insert into test values(1);
COMMIT;
dbms_output.put_line('PRINT SOMETHING 1');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
/
Second anonymous PL/SQL block:
set serveroutput on;
BEGIN
insert into test values(6);
COMMIT;
dbms_output.put_line('PRINT SOMETHING');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
/
If you create a stored procedure, you have more control and can exit whenever you like with a return statement.
So create a stored proc:
create or replace procedure myProc as
begin
dbms_ouput.put_line('i am here');
return;
dbms_ouput.put_line('and not here');
end;
Then in sqlplus or developer:
exec myProc();
You can nest the blocks into a single 'program unit'.
In this way an exception in the first block will stop the whole program unit from executing, rather than just being limited in scope to the first block.
set serveroutput on;
BEGIN
BEGIN
insert into test values(1);
insert into test values(1);
COMMIT;
dbms_output.put_line('PRINT SOMETHING 1');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
BEGIN
insert into test values(6);
COMMIT;
dbms_output.put_line('PRINT SOMETHING');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
end if;
return;
END;
END;
/
You should be able to use "exit" - see the Oracle documentation here: http://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12023.htm
Note that this will end your SqlPlus session, but I don't know of another way of doing it aside from using a single block or stored procedure.
Another useful statement is:
WHENEVER SQLERROR EXIT SQL.SQLCODE
Oracle documentation: http://docs.oracle.com/cd/B19306_01/server.102/b14357/ch12052.htm
Thanks for your valuable comments.
JoshL, i tried using EXIT but i am ending up with error. Please correct my code( I am new to PL/SQL). "WHENEVER SQLERROR EXIT" is good to use but my issue is that I use these sql scriptsd in InstallShield, so InstallShield installers does not recognize these statements and throws error.
set serveroutput on;
BEGIN
insert into test values(1);
insert into test values(1);
COMMIT;
dbms_output.put_line('PRINT SOMETHING 1');
EXCEPTION
WHEN OTHERS THEN
if sqlcode <> 0
then
dbms_output.put_line(SQLCODE || ' ' || SQLERRM);
RAISE;
exit;
end if;
END;
/
The EXIT command is only for use within a loop in PL/SQL. The EXIT command leaves the loop at that point. If you use the EXIT command outside a loop in PL/SQL the compiler throws an error.
The EXIT command in SQLPlus exits the SQLPlus session.
This is confusing, because they are two different Oracle products. SQL*Plus can run PL/SQL and the EXIT statement is a valid statement in both products, but with different contexts.

Oracle pl sql exception block will be executed when

If i have an oracle pl/sql procedure, I define an exception block like this:
exception
WHEN OTHERS THEN
errMsg := substr(SQLERRM, 1, 100);
dbms_output.put_line ('--> Error ALIAS1: ' || errMsg);
end;
Will this be executed no matter what kind of exception is raised?
WHEN OTHERS is a catch-all for any PL/SQL exception that isn't handled explicitly by its own WHEN clause.
It isn't normally a good idea to use it, for that reason; it's easy to accidentally trap and lose an important message about something bad happening. You should catch and handle the specific exceptions you expect to encounter. If you do want to record or log any other exceptions you should include a RAISE in the exception handler so everything else gets propagated normally.
...
exception
when others then
errMsg := substr(SQLERRM, 1, 100);
dbms_output.put_line ('--> Error ALIAS1: ' || errMsg);
raise;
end;
Read more about exceptions generally here, and about OTHERS here.

Resources