04Aug
By: Steven Feuerstein On: August 4, 2021 In: APEX Developer Solutions, Feuertips Comments: 0

Like sqlcode, sqlerrm is a PL/SQL function with a very simple purpose in life: it “returns the error message associated with an error code.” https://docs.oracle.com/database/121/LNPLS/sqlerrm_function.htm#LNPLS01350

Which is true, as far it goes. That word “associated” is kind of interesting. Here’s another way to put it:

sqlerrm returns the error message associated with the error code passed to it. If no code is provided, then the value returned by sqlcode is used.

Like sqlcode, sqlerrm is both straightforward and also a bit mysterious. Not the least because Oracle also says: “DBMS_UTILITY.FORMAT_ERROR_STACK is recommended over SQLERRM, unless you use the FORALL statement with its SAVE EXCEPTIONS clause.”

So, hey, like: “Don’t use it!”

Huh?

Let’s explore!

First, like sqlcode, you cannot call sqlerrm directly from within a SQL statement, even (and especially) one that is located inside a PL/SQL block. In other words, if you want to insert error information into your error log, you cannot do this:

exception 
   when others then
      insert into error_log (errcode, errmsg) values (sqlcode, sqlerrm);

Instead, you should call those functions, assign the values to PL/SQL variables, and then use those as in:

exception 
   when others then
      declare
         l_code integer := sqlcode;
         l_emsg varchar2(512) := sqlerrm;
      begin
         insert into error_log (errcode, errmsg) values (l_code, l_emsg);
      end;

Or, as I discovered (was informed by Niels Hecker), you can qualify the function names and then call them in SQL, as in:

exception 
   when others then
      insert into error_log (errcode, errmsg) values 
         (sys.standard.sqlcode, sys.standard.sqlerrm);

I still do not know why this works and I have not been able to get an explanation from Friends@Oracle, but it is what it is!

Next, why would Oracle recommend that you not use sqlerrm? Because (a) it only returns the error message, not the error stack, and (b) the string it returns is restricted to 512 bytes.

It is likely that you never even thought about error stacks. What else would you have or want to have besides the error message?

To see this, when I run this block….

declare
   procedure proc1 is
   begin
      raise no_data_found;
   end;
   
   procedure proc2 is
   begin
      proc1;
   exception
      when others then
         raise value_error;
   end;
   
   procedure proc3 is
   begin
      proc2;
   exception
      when others then
         raise program_error;
   end;
begin
   proc3;
exception
   when others then
      dbms_output.put_line(sqlerrm);
end;

I see:

ORA-06501: PL/SQL: program error

But if I replace the code in the handler to:

dbms_output.put_line(dbms_utility.format_error_stack);

then I see the following:

ORA-06501: PL/SQL: program error
ORA-06512: at line 23
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 23
ORA-01403: no data found
ORA-06512: at line 23

There’s the stack! It shows how the exception propagates out to the point at which you ask for the message/stack. Maybe you don’t care about that, but it can give you lots of insight into your code. So why not store the full stack instead of just the message?

And that’s why Oracle recommends you call dbms_utility.format_error_stack instead of sqlerrm. But that only makes sense for invocations of sqlerrm that do not pass an argument.

To understand what I mean, let’s go back to my definition of this built-in function:

sqlerrm returns the error message associated with the error code passed to it. If no code is provided, then the value returned by sqlcode is used.

Most developers probably don’t even realize you can pass an argument to sqlerrm. They simply call it to get the error message associated with the current error code (sqlcode) and then store that string in their error log (or display it).

But what if you have the error code and not the message? Then you can use sqlerrm to look up the “generic” message associated with that code.

Take a look at the output from this block:

declare
   l_codes dbms_sql.number_table := dbms_sql.number_table(
      0  => -1,
      1  => 100,
      2  => - 1403,
      3  => - 1422,
      4  => 1422,
      5  => 1,
      6  => -20000,
      7  => -21000,
      8  => -21002,
      9  => -50000
   );
begin
   for indx in l_codes.first..l_codes.last loop
      begin
         dbms_output.put_line('sqlerrm('||l_codes(indx)||') => ' || sqlerrm(l_codes(indx)));
      exception
         when others then
            dbms_output.put_line('Unable to call sqlerrm with code ' || l_codes(indx));
      end;
   end loop;
end;
/

sqlerrm(-1) => ORA-00001: unique constraint (.) violated
sqlerrm(100) => ORA-01403: no data found
sqlerrm(-1403) => ORA-01403: no data found
sqlerrm(-1422) => ORA-01422: exact fetch returns more than requested number of rows
sqlerrm(1422) =>  -1422: non-ORACLE exception 
sqlerrm(1) => User-Defined Exception
sqlerrm(-20000) => ORA-20000: 
sqlerrm(-21000) => ORA-21000: error number argument to raise_application_error of  is out of range
sqlerrm(-21002) => ORA-21002: Message 21002 not found;  product=RDBMS; facility=ORA
sqlerrm(-50000) => ORA-50000: Message 50000 not found;  product=RDBMS; facility=ORA

Here are the rules governing sqlerrm return strings:

  • If the value of error_code is +100, SQLERRM returns ORA-01403.
  • If the value of error_code is a positive number other than +100, SQLERRM returns this message:
  • If the value of error_code is a negative number whose absolute value is an Oracle Database error code, SQLERRM returns the error message associated with that error code. For example:
  • If the value of error_code is a negative number whose absolute value is not an Oracle Database error code, SQLERRM returns this message: ORA-error_code: Message error_code not found; product=RDBMS;
    facility=ORA

Hopefully you see what I mean by the “generic” error message.

You cannot use dbms_utility.format_error_stack in the same way. It accepts no arguments. It doesn’t look up error messages for you. It just returns the error stack. Which is a fine purpose in life to fulfill.

OK but what’s with FORALL?

Are you wondering why Oracle says: “DBMS_UTILITY.FORMAT_ERROR_STACK is recommended over SQLERRM, unless you use the FORALL statement with its SAVE EXCEPTIONS clause”? That’s because when the SQL engine passes an error back to the PL/SQL engine, the unsigned error code is saved for later inspection, but not the error message. Plus, you could have more than one error. So you will likely want to use sqlerrm to get the generic error message for that code. You can’t do that with dbms_utility.format_error_stack.

Watch the full Feuertips episode here

Feuertips

Every Wednesday at 11 am ET, I go live on YouTube and Facebook with Michelle Skamene to provide you with a not-to-be-missed PL/SQL tip and fun conversation. Sure, we record it so you can always watch later, but live is so much more rewarding!
Speaking of rewards, Steven finishes each show with a quick quiz to see if you were listening. Participation, of course, is optional!
One lucky quiz answerer will be selected at random to choose from three organizations dedicated to the preservation of our planet and its biodiversity: The Sheldrick Wildlife Trust, The Center for Biological Diversity, and Earthwatch.
Insum will then make a donation of $25 to that group in your name.
What could be better than levelling up your PL/SQL tips and helping one of these worthy organizations? Join us Wednesdays at 11!
Share this:
Share

Leave reply:

Your email address will not be published. Required fields are marked *