Oracle PL / SQL: genere una excepción definida por el usuario con SQLERRM personalizado

79

¿Es posible crear excepciones definidas por el usuario y poder cambiar SQLERRM?

Por ejemplo:

DECLARE
    ex_custom       EXCEPTION;
BEGIN
    RAISE ex_custom;
EXCEPTION
    WHEN ex_custom THEN
        DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

El resultado es "Excepción definida por el usuario". ¿Es posible cambiar ese mensaje?

EDITAR: Aquí hay algunos detalles más.

Espero que este ilustre mejor lo que trato de hacer.

DECLARE
    l_table_status      VARCHAR2(8);
    l_index_status      VARCHAR2(8);
    l_table_name        VARCHAR2(30) := 'TEST';
    l_index_name        VARCHAR2(30) := 'IDX_TEST';
    ex_no_metadata      EXCEPTION;
BEGIN

    BEGIN
        SELECT  STATUS
        INTO    l_table_status
        FROM    USER_TABLES
        WHERE   TABLE_NAME      = l_table_name;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            -- raise exception here with message saying
            -- "Table metadata does not exist."
            RAISE ex_no_metadata;
    END;

    BEGIN
        SELECT  STATUS
        INTO    l_index_status
        FROM    USER_INDEXES
        WHERE   INDEX_NAME      = l_index_name;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            -- raise exception here with message saying
            -- "Index metadata does not exist."
            RAISE ex_no_metadata;
    END;

EXCEPTION
    WHEN ex_no_metadata THEN
        DBMS_OUTPUT.PUT_LINE('Exception will be handled by handle_no_metadata_exception(SQLERRM) procedure here.');
        DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

En realidad, hay docenas de esos sub-bloques. Me pregunto si hay una manera de tener una única excepción definida por el usuario para que se genere cada uno de esos sub-bloques, pero que dé un mensaje diferente, en lugar de crear una excepción definida por el usuario separada para cada sub-bloque.

En .NET, sería como tener una excepción personalizada como esta:

    public class ColorException : Exception
    {
        public ColorException(string message)
            : base(message)
        {
        }
    }

Y luego, un método tendría algo como esto:

        if (isRed)
        {
            throw new ColorException("Red is not allowed!");
        }

        if (isBlack)
        {
            throw new ColorException("Black is not allowed!");
        }

        if (isBlue)
        {
            throw new ColorException("Blue is not allowed!");
        }
tgxiii
fuente

Respuestas:

149

Si. Solo tienes que usar la RAISE_APPLICATION_ERRORfunción. Si también desea nombrar su excepción, deberá usar el EXCEPTION_INITpragma para asociar el número de error a la excepción nombrada. Algo como

SQL> ed
Wrote file afiedt.buf

  1  declare
  2    ex_custom EXCEPTION;
  3    PRAGMA EXCEPTION_INIT( ex_custom, -20001 );
  4  begin
  5    raise_application_error( -20001, 'This is a custom error' );
  6  exception
  7    when ex_custom
  8    then
  9      dbms_output.put_line( sqlerrm );
 10* end;
SQL> /
ORA-20001: This is a custom error

PL/SQL procedure successfully completed.
Justin Cave
fuente
1
¡Precisamente lo que necesito! Supongo que hice mi edición cuando ya había respondido a mi pregunta. Muchas gracias.
tgxiii
39

Podrías usar RAISE_APPLICATION_ERROR así:

DECLARE
    ex_custom       EXCEPTION;
BEGIN
    RAISE ex_custom;
EXCEPTION
    WHEN ex_custom THEN
        RAISE_APPLICATION_ERROR(-20001,'My exception was raised');
END;
/

Eso generará una excepción que se parece a:

ORA-20001: My exception was raised

El número de error puede estar comprendido entre -20001 y -20999.

Tony Andrews
fuente
21

Por lo general, pierdo la pista de todos mis -20001códigos de error de tipo, así que trato de consolidar todos los errores de mi aplicación en un paquete agradable como este:

SET SERVEROUTPUT ON

CREATE OR REPLACE PACKAGE errors AS
  invalid_foo_err EXCEPTION;
  invalid_foo_num NUMBER := -20123;
  invalid_foo_msg VARCHAR2(32767) := 'Invalid Foo!';
  PRAGMA EXCEPTION_INIT(invalid_foo_err, -20123);  -- can't use var >:O

  illegal_bar_err EXCEPTION;
  illegal_bar_num NUMBER := -20156;
  illegal_bar_msg VARCHAR2(32767) := 'Illegal Bar!';
  PRAGMA EXCEPTION_INIT(illegal_bar_err, -20156);  -- can't use var >:O

  PROCEDURE raise_err(p_err NUMBER, p_msg VARCHAR2 DEFAULT NULL);
END;
/

CREATE OR REPLACE PACKAGE BODY errors AS
  unknown_err EXCEPTION;
  unknown_num NUMBER := -20001;
  unknown_msg VARCHAR2(32767) := 'Unknown Error Specified!';

  PROCEDURE raise_err(p_err NUMBER, p_msg VARCHAR2 DEFAULT NULL) AS
    v_msg VARCHAR2(32767);
  BEGIN
    IF p_err = unknown_num THEN
      v_msg := unknown_msg;
    ELSIF p_err = invalid_foo_num THEN
      v_msg := invalid_foo_msg;
    ELSIF p_err = illegal_bar_num THEN
      v_msg := illegal_bar_msg;
    ELSE
      raise_err(unknown_num, 'USR' || p_err || ': ' || p_msg);
    END IF;

    IF p_msg IS NOT NULL THEN
      v_msg := v_msg || ' - '||p_msg;
    END IF;

    RAISE_APPLICATION_ERROR(p_err, v_msg);
  END;
END;
/

Luego llame errors.raise_err(errors.invalid_foo_num, 'optional extra text')para usarlo, así:

BEGIN
  BEGIN
    errors.raise_err(errors.invalid_foo_num, 'Insufficient Foo-age!');
  EXCEPTION
    WHEN errors.invalid_foo_err THEN
      dbms_output.put_line(SQLERRM);
  END;

  BEGIN
    errors.raise_err(errors.illegal_bar_num, 'Insufficient Bar-age!');
  EXCEPTION
    WHEN errors.illegal_bar_err THEN
      dbms_output.put_line(SQLERRM);
  END;

  BEGIN
    errors.raise_err(-10000, 'This Doesn''t Exist!!');
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
  END;
END;
/

produce esta salida:

ORA-20123: Invalid Foo! - Insufficient Foo-age!
ORA-20156: Illegal Bar! - Insufficient Bar-age!
ORA-20001: Unknown Error Specified! - USR-10000: This Doesn't Exist!!
MassuguGo
fuente
1
Buen consejo! ¡Fue muy útil para mi proyecto!
SnakeSheet
1
Ésta es una buena práctica. Dos problemas menores en el pensamiento de raise_application_error : 1) el tamaño del segundo parámetro está limitado a 2048 bytes y 2) Preferiría que el tercer parámetro sea true(en lugar del predeterminado false) para obtener un seguimiento de pila completo.
user272735
5
declare
   z exception;

begin
   if to_char(sysdate,'day')='sunday' then
     raise z;
   end if;

   exception 
     when z then
        dbms_output.put_line('to day is sunday');
end;
Nagaraju Nampally
fuente
2
create or replace PROCEDURE PROC_USER_EXP 
AS
duplicate_exp EXCEPTION;
PRAGMA EXCEPTION_INIT( duplicate_exp, -20001 );
LVCOUNT NUMBER;
BEGIN
  SELECT COUNT(*) INTO LVCOUNT FROM JOBS WHERE JOB_TITLE='President';
  IF LVCOUNT >1 THEN 
   raise_application_error( -20001, 'Duplicate president customer excetpion' );
  END IF;

  EXCEPTION 
  WHEN duplicate_exp THEN 
  DBMS_OUTPUT.PUT_LINE(sqlerrm);
END PROC_USER_EXP;

La salida de ORACLE 11g será así:

Connecting to the database HR. 
ORA-20001: Duplicate president customer excetpion 
Process exited. 
Disconnecting from the database HR
Raj Sharma
fuente
La salida de ORACLE 11g será así: -Conectando a la base de datos HR. ORA-20001: Excedencia de cliente presidente duplicada. Proceso finalizado. Desconectarse de la base de datos HR.
Raj Sharma
Esto es bueno excepto (irónicamente) por el bloque EXCEPCIÓN. En casi todas las circunstancias, las excepciones manejadas en una cláusula WHEN deben volver a aparecer en el programa de llamada. El programa que llama necesita saber que la rutina llamada falló. El simple uso de DBMS_OUTPUT no es lo suficientemente bueno, porque el programa de llamada podría no ser capaz de procesar el búfer de salida del servidor y / o porque no fuerza al programa de llamada a reconocer el error.
APC