jueves, 28 de febrero de 2008

Excepciones en PL/SQL

Manejo de excepciones

En PL/SQL una advertencia o condición de error es llamada una excepción.

Las excepciones se controlan dentro de su propio bloque.La estructura de bloque de una excepción se muestra a continuación.


DECLARE
-- Declaraciones
BEGIN
-- Ejecucion
EXCEPTION
-- Excepcion
END;


Cuando ocurre un error, se ejecuta la porción del programa marcada por el bloque EXCEPTION, transfiriéndose el control a ese bloque de sentencias.

El siguiente ejemplo muestra un bloque de excepciones que captura las excepciones NO_DATA_FOUND y ZERO_DIVIDE. Cualquier otra excepcion será capturada en el bloque WHEN OTHERS THEN.


DECLARE
-- Declaraciones
BEGIN
-- Ejecucion
EXCEPTION
WHEN NO_DATA_FOUND THEN

-- Se ejecuta cuando ocurre una excepcion
de tipo NO_DATA_FOUND
WHEN ZERO_DIVIDE THEN
-- Se ejecuta cuando ocurre una excepcion
de tipo ZERO_DIVIDE


WHEN OTHERS THEN
-- Se ejecuta cuando ocurre una excepcion
de un tipo no tratado
-- en los bloques anteriores


END;

Como ya hemos dicho cuando ocurre un error, se ejecuta el bloque EXCEPTION, transfiriéndose el control a las sentencias del bloque. Una vez finalizada la ejecución del bloque de EXCEPTION no se continua ejecutando el bloque anterior.

Si existe un bloque de excepcion apropiado para el tipo de excepción se ejecuta dicho bloque. Si no existe un bloque de control de excepciones adecuado al tipo de excepcion se ejecutará el bloque de excepcion WHEN OTHERS THEN (si existe!). WHEN OTHERS debe ser el último manejador de excepciones.

Las excepciones pueden ser definidas en forma interna o explícitamente por el usuario. Ejemplos de excepciones definidas en forma interna son la división por cero y la falta de memoria en tiempo de ejecución. Estas mismas condiciones excepcionales tienen sus propio tipos y pueden ser referenciadas por ellos: ZERO_DIVIDE y STORAGE_ERROR.

Las excepciones definidas por el usuario deben ser alcanzadas explícitamente utilizando la sentencia RAISE.

Con las excepciones se pueden manejar los errores cómodamente sin necesidad de mantener múltiples chequeos por cada sentencia escrita. También provee claridad en el código ya que permite mantener las rutinas correspondientes al tratamiento de los errores de forma separada de la lógica del negocio.

Excepciones predefinidas

PL/SQL proporciona un gran número de excepciones predefinidas que permiten controlar las condiciones de error más habituales.

Las excepciones predefinidas no necesitan ser declaradas. Simplemente se utilizan cuando estas son lanzadas por algún error determinado.

La siguiente es la lista de las excepciones predeterminadas por PL/SQL y una breve descripción de cuándo son accionadas:

  • ACCESS_INTO_NULL/-6530 El programa intentó asignar valores a los atributos de un objeto no inicializado

  • COLLECTION_IS_NULL/-6531 El programa intentó asignar valores a una tabla anidada aún no inicializada
  • CURSOR_ALREADY_OPEN/-6511 El programa intentó abrir un cursor que ya se encontraba abierto. Recuerde que un cursor de ciclo FOR automáticamente lo abre y ello no se debe especificar con la sentencia
  • OPEN DUP_VAL_ON_INDEX/-1 El programa intentó almacenar valores duplicados en una columna que se mantiene con restricción de integridad de un índice único (unique index)
  • INVALID_CURSOR/-1001 El programa intentó efectuar una operación no válida sobre un cursor
  • INVALID_NUMBER/-1722 En una sentencia SQL, la conversión de una cadena de caracteres hacia un número falla cuando esa cadena norepresenta un número válido
  • LOGIN_DENIED/-1017 El programa intentó conectarse a Oracle con un nombre de usuario o password inválido
  • NO_DATA_FOUND/100 Una sentencia SELECT INTO no devolvió valores o el programa referenció un elemento no inicializado en una tablaindexada
  • NOT_LOGGED_ON/1012 El programa efectuó una llamada a Oracle sin estar conectado
  • PROGRAM_ERROR/-6501 PL/SQL tiene un problema interno
  • ROWTYPE_MISMATCH/-6504 Los elementos de una asignación (el valor a asignar y la variable que lo contendrá) tienen tipos incompatibles. También se presenta este error cuando un parámetro pasado a un subprograma no es del tipo esperado
  • SELF_IS_NULL/-30625 El parámetro SELF (el primero que es pasado a un método MEMBER) es nulo
  • STORAGE_ERROR/-6500 La memoria se terminó o está corrupta
  • SUBSCRIPT_BEYOND_COUNT/-6533 El programa está tratando de referenciar un elemento de un arreglo indexado que se encuentra en una posición más grande que el número real de elementos de la colección
  • SUBSCRIPT_OUTSIDE_LIMIT/-6532 El programa está referenciando un elemento de un arreglo utilizando un número fuera del rango permitido (por ejemplo, el elemento “-1”)
  • SYS_INVALID_ROWID/-1410 La conversión de una cadena de caracteres hacia un tipo rowid falló porque la cadena no representa un número
  • TIMEOUT_ON_RESOURCE/51 Se excedió el tiempo máximo de espera por un recurso en Oracle
  • TOO_MANY_ROWS/1422 Una sentencia SELECT INTO devuelve más de una fila
  • VALUE_ERROR/-6502 Ocurrió un error aritmético, de conversión o truncamiento. Por ejemplo, sucede cuando se intenta calzar un valor muy grande dentro de una variable más pequeña
  • ZERO_DIVIDE/-1476 El programa intentó efectuar una división por cero


Excepciones definidas por el usuario

PL/SQL permite al usuario definir sus propias excepciones, las que deberán ser declaradas y lanzadas explícitamente utilizando la sentencia RAISE.

Las excepciones deben ser declaradas en el segmento DECLARE de un bloque, subprograma o paquete. Se declara una excepción como cualquier otra variable, asignandole el tipo EXCEPTION. Las mismas reglas de alcance aplican tanto sobre variables como sobre las excepciones.


DECLARE
-- Declaraciones

MyExcepcion EXCEPTION;
BEGIN
-- Ejecucion
EXCEPTION
-- Excepcion
END;

Reglas de Alcance

Una excepcion es válida dentro de su ambito de alcance, es decir el bloque o programa donde ha sido declarada. Las excepciones predefinidas son siempre válidas.

Como las variables, una excepción declarada en un bloque es local a ese bloque y global a todos los sub-bloques que comprende.

La sentencia RAISE

La sentencia RAISE permite lanzar una excepción en forma explícita. Es posible utilizar esta sentencia en cualquier lugar que se encuentre dentro del alcance de la excepción.


DECLARE

-- Declaramos una excepcion identificada por VALOR_NEGATIVO

VALOR_NEGATIVO EXCEPTION;

valor NUMBER;
BEGIN
-- Ejecucion

valor := -1;

IF valor <> THEN

RAISE VALOR_NEGATIVO;

END IF;


EXCEPTION
-- Excepcion

WHEN VALOR_NEGATIVO THEN

dbms_output.put_line('El valor no puede ser negativo');
END;

Con la sentencia RAISE podemos lanzar una excepción definida por el usuario o predefinida, siendo el comportamiento habitual lanzar excepciones definidas por el usuario.

Recordar la existencia de la excepción OTHERS, que simboliza cualquier condición de excepción que no ha sido declarada. Se utiliza comúnmente para controlar cualquier tipo de error que no ha sido previsto. En ese caso, es común observar la sentencia ROLLBACK en el grupo de sentencias de la excepción o alguna de las funciones SQLCODESQLERRM, que se detallan en el próximo punto.

Uso de SQLCODE y SQLERRM

Al manejar una excepción es posible usar las funciones predefinidas SQLCode y SQLERRM para aclarar al usuario la situación de error acontecida.

SQLcode devuelve el número del error de Oracle y un 0 (cero) en caso de exito al ejecutarse una sentencia SQL.

Por otra parte, SQLERRM devuelve el correspondiente mensaje de error.

Estas funciones son muy útiles cuando se utilizan en el bloque de excepciones, para aclarar el significado de la excepción OTHERS.

Estas funciones no pueden ser utilizadas directamente en una sentencia SQL, pero sí se puede asignar su valor a alguna variable de programa y luego usar esta última en alguna sentencia.


DECLARE
err_num NUMBER;
err_msg VARCHAR2(255);
result NUMBER;
BEGIN
  SELECT 1/0 INTO result
FROM DUAL;

EXCEPTION
WHEN OTHERS THEN

err_num := SQLCODE;
err_msg := SQLERRM;
  DBMS_OUTPUT.put_line('Error:'||TO_CHAR(err_num));
DBMS_OUTPUT.put_line(err_msg);
END;

También es posible entregarle a la función SQLERRM un número negativo que represente un error de Oracle y ésta devolverá el mensaje asociado.


DECLARE
msg VARCHAR2(255);
BEGIN
msg := SQLERRM(-1403);
DBMS_OUTPUT.put_line(MSG);
END;