Ben Maxfield
Ben Maxfield

Reputation: 655

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

Upvotes: 2

Views: 1819

Answers (2)

Chris Saxon
Chris Saxon

Reputation: 9805

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: 

Upvotes: 3

q4za4
q4za4

Reputation: 652

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

Upvotes: 0

Related Questions