You are on page 1of 32

Manual de Buenas Prcticas de Desarrollo en PL/SQL

Cdigo: Ttulo: Objetivo: Manual de Buenas Prcticas de Desarrollo en PL/SQL Describir los principios generales de Buenas Prcticas de Desarrollo en PLSQL estableciendo el marco necesario para asegurar caractersticas comunes para facilitar el desarrollo y mantenimiento de las aplicaciones. 12/12/2005 12/12/2005

Vigencia inicial : ltima revisin : ltimo revisor : Vigencia hasta : Autor: Revisor: Usuarios: Dirigir Consultas a:

ndice
........................................................................................................................................................................2 2.Pauta para variables y estructuras de datos..................................................................................................3 3.Pauta para declaracin y uso de variables de paquetes..............................................................................10 4.Pautas para el uso de estructuras de Control..............................................................................................14 5.Pautas para el manejo de excepciones.......................................................................................................18 6.Pautas mnimas para uso de SQL dentro de PL/SQL..................................................................................25 7.Reglas generales para performance ...........................................................................................................31 1.

2. Pauta para variables y estructuras de datos


Declarar, definir el tipo, inicializar y asignar valor por defecto a las estructura de datos antes de trabajar con ellas.

PL/SQL es un lenguaje fuertemente tipificado. Esto significa que antes de trabajar con cualquier tipo de estructura de datos, es necesario declarar las mismas, definir el tipo y opcionalmente inicializarlas o asignarles un valor por defecto. Las declaraciones deben realizarse en la seccin de declaracin de variables de un bloque annimo, procedimiento, funcin o paquete.

En las declaraciones que se relacionan a tablas y columnas utilizar la clusula %TYPE y %ROWTYPE.

Esta forma permite al cdigo adaptarse a cambios de estructuras de datos y autodocumentarse, ya que al leer el cdigo se puede saber a que tipo de dato hace referencia. Para el tipo VARCHAR2, Oracle separa la memoria necesaria segn la longitud definida. Es muy comn Hardcodear la longitud del mismo a su mxima tamao para evitar problemas, a costo de utilizar mas memoria de la necesaria. Aqu nuevamente se hace conveniente el uso de %TYPE o %SUBTYPE para un mejor aprovechamiento de los recursos de memoria.
Ejemplo: Mal: DECLARE l_apellido l_mensaje

VARCHAR2(45); VARCHAR2(2000);

Bien: DECLARE l_apellido l_mensaje

personas.nom_apellido%TYPE; par_mensajes.mensaje%TYPE;

En los datos numricos, definir la precisin.

Oracle soporta hasta 38 dgitos de precisin en los tipos NUMBER. Si no se define en forma correcta se est derrochando memoria.
Ejemplo: Mal: DECLARE l_importe

NUMBER;

Bien: DECLARE l_importe

NUMBER(12,2);

Usar declaracin de constantes para valores que no cambian durante la ejecucin del programa.

Permite que ciertos valores no sean modificados por otros desarrolladores y asegura que los datos que se consideran confidenciales o que se utilizan en frmulas o parametrizaciones no sean manipulados en los cdigos.
Ejemplo: DECLARE l_fec_actual DATE := TRUNC(SYSDATE); l_trj_banco VARCHAR2(4) := VISA;

Siempre que sea posible, utilizar el tipo de dato RECORD para manejar estructuras.

La declaracin de variables individuales o dispersin de datos, muchas veces, complica la lectura del cdigo. La agrupacin de los mismos bajo estructuras, facilita la administracin y el mantenimiento del cdigo.
Ejemplo: Mal: DECLARE

l_nombre personas.nom_nombre%TYPE; l_apellido personas.nom_apellido%TYPE; l_edad personas.num_edad%TYPE; Bien: DECLARE TYPE r_persona IS RECORD (nombre personas.nom_nombre%TYPE, apellido personas.nom_apellido%TYPE, edad personas.num_edad%TYPE);

Realizar inicializaciones complejas de variables en la seccin de ejecucin de programa.

Los errores de un cdigo solo pueden ser capturados en la seccin ejecutable de un bloque. Si la inicializacin de una variable en la seccin de declaracin falla, el error no se puede manejado. Para ello hay que asegurar que la inicializacin lgica no falle y esto se asegura haciendo las inicializaciones al comienzo de la seccin de ejecucin. Si se produce un error, se puede capturar el error y decidir su tratamiento.
Ejemplo: Mal: DECLARE l_last_calificacion calificaciones.valor%TYPE:=last_busqueda (SYSDATE); l_min_calificacion INTEGER:=a_calificacion.limits(a_calificacion.low); BEGIN Bien: DECLARE l_last_calificacion calificaciones.valor%TYPE; l_min_calificacion INTEGER; PROCEDURE init IS BEGIN l_last_calificacion := last_busqueda(SYSDATE); l_min_calificacion := a_calificacion.limits(a_calificacion.low); EXCEPTION -- Manejo de errores en el programa. END; BEGIN init;

Reemplazar expresiones complejas con variables booleanas y funciones.

Las expresiones booleanas se pueden evaluar con tres valores: TRUE, FALSE o NULL. Se pueden usar variables de este tipo para ocultar expresiones complejas. Como consecuencia se puede leer mas fcilmente el cdigo y es mas simple su mantenimiento.
Ejemplo: Mal: IF l_salario_total BETWEEN 10000 AND 50000 AND emp_estado(rec_empleado.numero) = 'N' AND (MONTHS_BETWEEN (rec_empleado.fec_ingreso, SYSDATE) > 10) THEN emp_informar(rec_empleado.empno); END IF; Bien: DECLARE condicion_para_informar BOOLEAN; BEGIN condicion_para_informar := total_sal BETWEEN 10000 AND 50000 AND emp_estado(rec_empleado.numero) = 'N' AND (MONTHS_BETWEEN (rec_empleado.fec_ingreso, SYSDATE) > 10; ......... IF condicion_para_informar THEN emp_informar(rec_empleado.empno); END IF; EXCEPTION WHEN OTHERS THEN -- definir el manejo de errores. END;

Remover variables y cdigo no usado.

En determinadas circunstancias los cdigos son modificados a medida que transcurre el tiempo, la lgica cambia y a veces se declaran variables que no se usan, se escribe cdigo que en un momento era necesario o que se quiere mantener activado por un momento, pero despus no se recuerda cual fue el motivo del mismo. Para mantener todo mas claro, es conveniente eliminar esos puntos que conducen a zonas muertas. Una prctica para esto consiste en eliminar las variables que solo aparecen en la zona de declaracin. Existen varios productos que simplifican esta tarea. Es mucho mas simple entender, realizar seguimientos o mantener cdigos que no tienen zonas muertas. A modo de ejemplo se incluye un cdigo con algunos errores:

PROCEDURE check_control (

p_nro_cuenta p_importe IS

IN cuentas.nro_cuenta%TYPE, IN VARCHAR2)

l_contador NUMBER(3); l_baja BOOLEAN; l_fecha_movimiento DATE := SYSDATE; BEGIN l_fecha_movimiento := r_cuenta.fecha_ult_mov (p_nro_cuenta); IF ADD_MONTHS (SYSDATE, -60) > l_fecha_movimiento THEN Informar_cuenta; ELSIF ADD_MONTHS (SYSDATE, -24) > l_fecha_movimiento THEN Check_baja (p_nro_cuenta, l_baja); IF l_baja AND /* agregado para forzar false */ FALSE THEN armar_carta (p_nro_cuenta); END IF; -- arma proceso para control -- pkg_procesos_cuenta.iniciar_control (p_nro_cuenta); END IF; END; Lo que se puede observar es lo siguiente: o o p_Importe esta declarado, no se usa y no tiene asignacin por defecto, por lo cual el valor es totalmente ignorado. l_contador esta declarada, pero no se usa. l_fecha_movimiento est asignado con sysdate e inmediatamente recibe el valor de r_cuenta.fecha_ult_mov (p_nro_cuenta);. La llamada a armar_carta se desactivo con el agregado de FALSE. La llamada a pkg_procesos_cuenta.iniciar_control esta comentada.

o
o o

Utilizar rutinas de cierre o limpieza cuando los programas terminan (en ejecucin normal o por error)

En algunos escenarios es crucial tener rutinas de cierre o limpieza, las cuales se deben incluir al finalizar el programa y al final de cada excepcin. Generalmente son aplicables para el uso de cursores y manejo de archivos. A continuacin se muestra ejemplos:
Ejemplo: Mal: DECLARE c_cursor NUMBER(2); f_file UTL_FILE.FILE_TYPE; BEGIN c_cursor := DBMS_SQL.OPEN_CURSOR; f_file := UTL_FILE.FOPEN (c:\temp,archivo.txt, R); . EXCEPTION WHEN NO_DATA_FOUND THEN log_error; RAISE;

END; Se puede observar que tanto el cursor, como el archivo quedan abiertos en caso de existir alguna excepcin. Bien: DECLARE c_cursor NUMBER(2); f_file UTL_FILE.FILE_TYPE; PROCEDURE limpiar IS IF c_cursor%ISOPEN THEN DBMS_SQL.CLOSE (c_cursor); END IF; UTL_FILE.FCLOSE(f_file); END limpiar; BEGIN c_cursor := DBMS_SQL.OPEN_CURSOR; f_file := UTL_FILE.FOPEN (c:\temp,archivo.txt, R); limpiar; EXCEPTION WHEN NO_DATA_FOUND THEN log_error; limpiar; raise; END; Ahora se puede ver que se defini la rutina que realiza el cierre de archivo y de cursor. Este tipo de prctica evita que se produzcan errores que indiquen que el archivo est en uso o que el cursor ya se encuentra abierto, cuando la rutina es llamada de distintos puntos, adems que facilita a otros desarrolladores agregar nuevas estructuras y contemplar el cierre o limpieza de las mismas.

Tener cuidado con las conversiones implcitas de tipos de datos.

Si bien es sabido que PL/SQL maneja las conversiones implcitas, existen al menos dos grandes problemas con esto. Las conversiones no son intuitivas, a veces se realizan de formas no esperadas y ocurren problemas, especialmente dentro de sentencias SQL. Las reglas de conversin no estn bajo el control de desarrollador. Pueden cambiar con el upgrade a una versin de Oracle o con el cambio de parmetros como NLS_DATE_FORMAT.

Se puede convertir con el uso explcito de funciones como son: TO_DATE, TO_CHAR, TO_NUMBER y CAST.
Ejemplo: Mal: DECLARE l_fecha_fin

DATE := 01-MAR-04;

Este cdigo puede dar error si los parmetros de inicializacin no corresponden a DD-MON-YY o DDMON-RR. Bien: DECLARE l_fecha_fin date := TO_DATE (01-MAR-04,DD-MON-RR); En este caso el uso de conversiones explcitas evita errores y no se esta sujeto a condiciones externas del programa.

3. Pauta para declaracin y uso de variables de paquetes. Agrupar los tipos de datos, evitar su exposicin y controlar su manipulacin.

Los paquetes requieren ciertos tipos de recaudos en la forma de declaracin y uso de variables. Agrupar los tipos de datos, evitar su exposicin y controlar su manipulacin permiten obtener las ventajas del uso de los mismos. Definir las constantes que son referenciadas por toda la aplicacin en un paquete nico. Definir las constantes que corresponden a un rea especfica dentro de un paquete que encapsula esa funcionalidad

Nunca colocar literales en duro, como SI o 150 en el cdigo. Es conveniente crear un paquete para mantener estos nombres y valores publicados en reemplazo de los literales.
Ejemplo para paquete general CREATE OR REPLACE PACKAGE CONSTANTS IS -- representacin de true o false falso CONSTANT CHAR(1) := F; verdadero CONSTANT CHAR(1) := V; -- fecha de control (6 meses antes) fecha_control CONSTANT DATE := ADD_MONTHS (SYSDATE, -6); Ejemplo para paquete especfico CREATE OR REPLACE semana_max activo inactivo codigo_pais PACKAGE cta_constants IS CONSTANT INTEGER := 54; CONSTANT CHAR(1) := A; CONSTANT CHAR(1) := I; CONSTANT INTEGER := 54;

Este tipo de prctica permite que el cdigo no luzca hard codeado, lo cual lo hace mas legible y mantenible y adems evita que los literales sean modificados.

Centralizar las definiciones de Types en las especificaciones del paquete.

A medida que se usan los features del lenguaje, se definirn distintos TYPE entre los que podemos incluir: SUBTYPEs que definen tipos especficos de la aplicacin.

Collection TYPEs, como lista de nmeros, fechas y records. Cursores referenciados Esto permite tener estandarizados los tipos de datos para que sean usados por mltiples programas. Los desarrolladores pueden escribir ms rpidamente y disminuir los bugs. Tambin simplifica el mantenimiento de los types, ya que solo es necesario modificar el/los paquetes donde estn declarados.
Ejemplo CREATE OR REPLACE PACKAGE colltype IS TYPE logicos_tab IS TABLE OF BOOLEAN; TYPE logicos_tab_idx IS TABLE OF BOOLEAN INDEX BY BINARY_INTEGER; TYPE fechas_tab IS TABLE OF DATE; TYPE fechas_tab_idx IS TABLE OF DATE INDEX BY BINARY_INTEGER; END colltype;

Disminuir el uso de variables globales en paquetes y en caso de hacerlo, solo en el cuerpo del paquete.

Una variable global es una estructura de datos que se puede referenciar fuera del alcance o bloque en donde est declarada. Cuando se declara una variable en un paquete, existe y retiene su valor durante la duracin de la sesin. Son peligrosas porque crean dependencias ocultas o efectos laterales. El seguimiento de las mismas es complejo, ya que es necesario ver la implementacin para ver sus instancias. Una solucin general para esto es pasar estas variables globales como un parmetro para no referenciarlas directamente en el programa.
Ejemplo FUNCTION obtener_multa ( p_cuenta IN clientes.nro_cuenta%TYPE ) IS l_dias_atraso NUMBER(3); BEGIN l_dias_atraso := determinar_atraso( p_cuenta, SYSDATE); return (l_dias_atraso * g_tasa_diaria); END obtener_multa; En esta funcin el retorno depende de la variable global g_tasa_diaria. Esto hace que se pierda flexibilidad ya que la funcin solo puede trabajar con el valor instanciado y adems, si no tiene valor, se corre el riesgo de que no trabaje en forma correcta. Para mejorar los resultados y reducir la interdependencia se puede agregar un parmetro:

FUNCTION obtener_multa ( p_cuenta IN clientes.nro_cuenta%TYPE, P_tasa_diaria IN NUMBER ) IS l_dias_atraso NUMBER(3); BEGIN l_dias_atraso := determinar_atraso( p_cuenta, SYSDATE); return (l_dias_atraso * g_tasa_diaria); END obtener_multa;

Exponer las variables globales de los paquetes usando get and set.

Cualquier estructura de datos declarada en la especificacin del paquete puede ser referenciada por cualquier programa con autorizacin de EXECUTE. Esto hace que en forma deliberada se pueda hacer uso de esas estructuras globales. Para evitar eso, es conveniente declarar estos datos en el cuerpo del paquete y proveer al paquete de mtodos get y set, declarados en la especificacin. Esta forma le permite a los desarrolladores, acceder a los datos a travs de estos programas y manipular los datos segn las reglas de estos procedimientos.
Ejemplo CREATE OR REPLACE PACKAGE pkg_calculo_gastos IS g_gasto_mensual NUMBER(3):= 20; FUNCTION dias_atraso (p_cuenta IN CTA.nro_cuenta%TYPE) RETURN INTEGER; END pkg_calculo_gastos; Facilmente podemos manipular los datos desde cualquier lugar haciendo mencin a: BEGIN pkg_calculo_gastos.g_gasto_mensual := 15; . Para evitar esto y poder manipular los datos segn las reglas del negocio se puede reescribir el cdigo agregando set y get de esta manera: CREATE OR REPLACE PACKAGE pkg_calculo_gastos IS FUNCTION dias_atraso (p_cuenta IN cuentas.nro_cuenta%TYPE) RETURN INTEGER; PROCEDURE set_gasto_mensual (p_gasto IN NUMBER); END pkg_calculo_gastos; CREATE OR REPLACE PACKAGE BODY pkg_calculo_gastos IS

g_gasto_mensual := 15; PROCEDURE set_gasto_mensual (p_gasto IN NUMBER) IS BEGIN g_gasto_mensual := GREATEST (LEAST (p_gasto, 30), 10); END set_gasto_mensual;

END pkg_calculo_gastos;

4. Pautas para el uso de estructuras de Control Las estructuras de control pueden convertirse en un punto de complejidad de un cdigo. La normalizacin del uso de RETURN y el EXIT garantiza rapidez en la comprensin de las lgicas escritas y facilidad en su mantenimiento.

Nunca se sale de una estructura repetitiva con RETURN o con EXIT.

Cada estructura repetitiva tiene un punto de control por donde se debe producir su salida. Un FOR loop itera desde el valor de comienzo hasta el valor de terminacin (ciclo N). Un WHILE loop itera mientras no se cumpla la condicin de terminacin. (Ciclo 0). Existe un LOOP con control de la condicin al final de ciclo (ciclo 1). Es LOOP EXIT WHEN condicin lgica.

Combinando estos ciclos se puede hacer que las estructuras tengan un nico punto de control para el ciclo repetitivo. Esto facilita la lectura, comprensin y mantenimiento del programa y permite hacer modificaciones en forma ms simple. Una funcin debe tener un nico RETURN exitoso como ltima lnea de la seccin ejecutable. Normalmente, cada manejador de excepciones puede retornar un valor.

En los programas largos donde se evalan muchas condiciones, la existencia de mltiples salidas dificulta el entendimiento y mantenimiento del cdigo.
Ejemplo LOOP

read_line (file1, line1, file1_eof); read_line (file2, line2, file2_eof); IF (file1_eof AND file2_eof) THEN RETURN (TRUE); ELSIF (line1 != line2) THEN RETURN (FALSE); ELSIF (file1_eof OR file2_eof) THEN RETURN ( FALSE); END IF; END LOOP; Aqu existen muchas salidas, por lo que esto se puede reemplazar por lo siguiente:

... LOOP read_line (file1, line1, file1_eof); read_line (file2, line2, file2_eof); IF (file1_eof AND file2_eof) THEN retval := TRUE; exit_loop := TRUE; ELSIF (line1 != line2) THEN retval := FALSE; exit_loop := TRUE; ELSIF (file1_eof OR file2_eof) THEN retval := FALSE; exit_loop := TRUE; END IF; EXIT WHEN exit_loop; END LOOP; RETURN (retval); En este caso se contemplan las condiciones en muchos puntos, pero se evala la salida solo en un lugar para despus realizar el RETURN.

Nunca declarar el ndice de un FOR ..LOOP.

PL/SQL ofrece dos tipos de FOR LOOP: Numricos y cursores. Ambos tienen este formato general: FOR indice_loop IN loop range LOOP Loop body END LOOP; El ndice del loop se declara implcitamente durante la ejecucin. El mbito de existencia est restringido al cuerpo del loop. Si se declarase el ndice, se estara generando otra variable completamente separada que en el mejor de los casos nunca ser usada, pero si se usa fuera del loop, puede introducir errores.
Ejemplo DECLARE CURSOR c_cuenta IS SELECT nro_cuenta FROM cuentas; r_cuenta cuentas%ROWTYPE; BEGIN FOR r_cuenta in c_cuenta LOOP

END; La lnea que se encuentra fuera del loop, retorna nulo, ya que est fuera del alcance del mismo.

Calcula_saldo(r_cuenta.nro_cuenta); END LOOP; DBMS_OUTPUT.PUT_LINE(r_cuenta.nro_cuenta);

La lnea que se encuentra fuera del loop, retorna nulo, ya que est fuera del alcance del mismo.

Al utilizar la clusula ELSIF, asegurarse que las condiciones son excluyentes

En el caso de sentencias que deban ingresar en un condicional si o si, se debe lanzar una excepcin en caso de que esto no ocurra y no presuponer que nunca se cumplir la condicin.

Ejemplo: DECLARE IF SEXO = M THEN <<sentencias>> ELSIF SEXO = F THEN

Reemplazar y simplificar el IF con variables booleanas.


Ejemplo DECLARE l_logica BOOLEAN; BEGIN IF l_logica = TRUE THEN ELSIF l_logica = FALSE THEN END IF; END; Reemplazar por DECLARE l_logica BOOLEAN; BEGIN IF l_logica THEN ELSIF NOT l_logica THEN END IF; END;

5. Pautas para el manejo de excepciones Aunque se escriba un cdigo perfecto, que no contenga errores y que nunca realice operaciones inapropiadas, el usuario podra usar el programa incorrectamente, produciendo una falla que no estaba contemplada. El uso de excepciones permite capturar y administrar los errores que se pueden producir dentro del cdigo. Su buen uso trae como resultado un cdigo con menos bugs y mas fcil de corregir. Establecer los lineamientos para el manejo de errores antes de comenzar a codificar.

Es imprctico definir secciones de excepciones en el cdigo despus de que el programa fue escrito. La mejor forma de implementar un manejo de errores en toda la aplicacin es usando paquetes que contengan al menos los siguientes elementos. Procedimientos que realicen el manejo de tareas de excepciones, como por ejemplo escribir un log de errores. Un programa que oculte la complejidad de RAISE_APPLICATION_ERROR y los nmeros de application-error. Una funcin que retorne el mensaje de error para un cdigo de error.

Utilizar el modelo por defecto de manejo de excepciones para propagar la comunicacin de errores.

Aprovechar la arquitectura de manejo de errores de PL/SQL y separar las excepciones en bloques. Evitar este tipo de cdigo.
BEGIN pkg_cuenta.determinar_estado (l_cuenta, error_code, error_msg); IF error_code != 0 THEN err.log (...); GOTO end_of_program; END IF; pkg_cuenta.imprimir_estado (l_cuenta, error_code, error_msg); IF error_code != 0 THEN err.log (...);

GOTO end_of_program; END IF; END;

La seccin de cdigo ejecutable debe estar limpia, simple y fcil de seguir. No es necesario controlar el estado despus de cada llamada a un programa. Simplemente debe incluirse una seccin de excepcin, capturar el error y determinar la accin a realizar.

Capturar todas las excepciones y convertir el significado de los cdigos de error en los retornos a programas que no son PL/SQL.

Cuando los programas PL/SQL son llamados por otros lenguajes de programacin (Java, Visual Basic, etc), al menos es necesario regresar el estado de error (cdigo y mensaje) para que administren los mismos. Una manera sencilla de hacer esto es sobrecargar el programa original con otro del mismo nombre y dos parmetros adicionales.
Ejemplo CREATE OR REPLACE PACKAGE pkg_cta_control IS PROCEDURE estado ( p_cuenta IN cuentas.nro_cuenta%TYPE); PROCEDURE estado ( p_cuenta IN cuentas.nro_cuenta%TYPE, error_code OUT INTEGER, error_msg OUT VARCHAR2); END pkg_cta_control;

Los desarrolladores pueden llamar al procedimiento que deseen y chequear los errores de la manera mas apropiada para sus programas.

Usar procedimientos propios de RAISE en lugar de las llamadas explicitas a RAISE_APPLICATION_ERROR.

Cuando se est controlando excepciones de sistemas como NO_DATA_FOUND, se usa RAISE, pero cuando se quiere controlar un error de aplicacin especfico, se usa RAISE_APPLICATION_ERROR. Para el ltimo caso, se debe asignar un nmero de error y mensaje, lo cual termina en un hardcodeo. Para ello se puede utilizar constantes para especificar un nmero. Una manera mas clara es proveer un procedimiento que automticamente controle el nmero de error y determina la forma de ejecutar el error.

Ejemplo RAISE_APPLICATION_ERROR ( -20734, 'Sueldo menor al mnimo'); Se podra escribir de la siguiente forma err.raise (errnums.sueldo_muy_bajo);

Los desarrolladores no tienen que determinar cual es el nmero de error que tienen que usar, solo pasan la constante que desean usar y dejan que la rutina determine cual es el RAISE correspondiente.

No sobrecargar una EXCEPTION con mltiples errores al menos que la prdida de informacin sea intencional.

No declarar una excepcin genrica como ERROR_GENERAL y luego ejecutar la excepcin en diferentes circunstancias. La lectura del cdigo traer problemas al intentar cual fue la causa de problema. La excepcin NO_DATA_FOUND se puede utilizar para indicar que no se encontraron filas en la ejecucin de un query o para indicar que se lleg al final de un archivo. Una forma de separa esto puede ser de la siguiente manera.
Ejemplo PROCEDURE lecturas IS l_cuenta cuentas.nro_cuenta%TYPE; l_linea VARCHAR2(1023); fid UTL_FILE.FILE_TYPE; no_table_data EXCEPTION; no_file_data EXCEPTION; BEGIN BEGIN -SELECT nro_cuenta INTO l_cuenta FROM usrdemo.cuentas WHERE 1 = 2; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE no_table_data; END; -BEGIN fid := UTL_FILE.FOPEN ('c:\temp', 'texto.txt', 'R'); UTL_FILE.GET_LINE (fid, l_linea); UTL_FILE.GET_LINE (fid, l_linea);

EXCEPTION WHEN NO_DATA_FOUND THEN RAISE no_file_data; END; -EXCEPTION WHEN no_table_data THEN pl ('Sin filas!'); WHEN no_file_data THEN pl ('Leyendo fin de archivo!'); END lecturas;

No Manejar excepciones que no pueden evitarse, pero si anticiparse.

Si cuando se est escribiendo un programa, se puede predecir que un error va a ocurrir, se debera incluir un manejador para ese caso.
Ejemplo PROCEDURE get_sucursal (p_cuenta cuentas.nro_cuenta%TYPE) IS l_sucursal cuentas.nro_sucursal%TYPE; BEGIN SELECT nro_sucursal INTO l_sucursal FROM usrdemo.cuentas WHERE nro_cuenta=p_cuenta; RETURN l_sucursal; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; END;

Evitar exponer cdigo en duro para el manejo de errores, reemplazar por procedimientos que administren los mismos.

Evitar exponer cdigo en duro para el manejo de errores, reemplazar por procedimientos que administren los mismos. La mejor manera de registrar en forma consistente y tener mantener la calidad del seguimiento de errores por toda la aplicacin es ofreciendo un conjunto de procedimientos predefinidos que canalicen el manejo de los mismos. Es fundamental que el equipo de desarrollo use siempre solamente estos procedimientos en sus clusulas WHEN.

Ejemplo

EXCEPTION WHEN NO_DATA_FOUND THEN l_msg := Cuenta sin titular || to_char (p_cuenta); l_err := SQLCODE; l_prog := EJEMPLO ; INSERT INTO usrdemo.errlog (cod_error, des_msg, num_linea, fec_ocurrencia, cod_usuario) VALUES (l_err, l_msg, l_prog, SYSDATE, USER); END;

En este ejemplo se ve como se expone el mtodo de logeo, en caso de cambiar la estructura de la tabla, nos encontramos con un problema grande. Por otro lado se est hardcodeando el nombre del programa, el cual se puede recuperar con DBMS_UTILITY.FORMAT_CALL_STACK. Una mejor forma de hacer esto sera:
EXCEPTION WHEN NO_DATA_FOUND THEN Err.handle (Cuenta sin titular ||to_char(p_cuenta), log => TRUE, reraise => FALSE); END;

Esta manera permite que todos manejen el error de la misma forma y hace ms simple el mantenimiento en caso de querer agregar funcionalidad a la administracin de los mismos. Usar nombres constantes para flexibilizar los nmeros y mensajes de errores de aplicacin

Oracle define 1000 nmeros de error, entre 20000 y 20999 para usar para nuestra aplicacin. Se recomienda definir todos los errores en una tabla o un archivo del sistema operativo, construir paquetes para manipular esos datos y ejecutar el RAISE usando esos nombres y no cdigos en duro.

Ejemplo PROCEDURE check_sueldol ( p_sueldo personas.sueldo%TYPE) IS BEGIN IF p_sueldo < constantes.sueldo_minimo THEN RAISE_APPLICATION_ERROR(-20734, El sueldo mnimo es || constantes.sueldo_minimo); END IF; END check_sueldol;

Un cdigo mas apropiado para esto sera:


PROCEDURE check_sueldol ( p_sueldo personas.sueldo%TYPE) IS BEGIN IF p_sueldo < constantes.sueldo_minimo THEN err.raise(errnums.check_sueldo); END IF; END check_sueldol;

Los desarrolladores evitan el conflicto de sobrecargar el mismo nmero de error con mensajes distintos y solo tienen que llamar a err.raise del procedimiento que realizar todo el trabajo.

Usar WHEN OTHERS solo para excepciones desconocidas que deben ser ejecutadas.

No es conveniente usar WHEN OTHERS para cualquier error. Si se puede conocer el tipo de excepcin que se quiere ejecutar, es conveniente administrarla en forma especfica.
Ejemplo EXCEPTION WHEN OTHERS THEN IF SQLCODE = -1 THEN Update_instancia(..); ELSE Err.log; RAISE; END IF; END ;

Esta forma es mucho mas clara.


EXCEPTION WHEN DUP_VAL_ON_INDEX THEN Update_instancia(..); WHEN OTHERS THEN Err.log; RAISE; END ;

Es mas fcil leer lo que se espera que haga el cdigo y adems se evita el hardcodeo de nmeros de errores.

6. Pautas mnimas para uso de SQL dentro de PL/SQL La escritura de S QL dentro del cdigo es uno de los aspectos ms flexibles de PL/SQL y tambin puede ser uno de los ms peligrosos. El establecimiento de reglas claras para su inclusin permite asegurar beneficios y buenos resultados.

Usar transacciones autnomas para aislar los efectos del ROLLBACK y COMMIT

Permite grabar o realizar rollback dentro de un cdigo PL/SQL sin afectar la sesin principal. Para hacer una transaccin autnomas es necesario incluir en seccin de declaracin la siguiente sentencia. PRAGMA AUTONOMOUS_TRANSACTIONS;
Ejemplo PROCEDURE log_error ( p_code IN error_log.errcode%TYPE, p_msg IN error_log.errtext%TYPE) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO usrdemo.error_log (errcode, errtext, created_on, created_by) VALUES(code, msg, SYSDATE, USER); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; END log_error;

Con este tipo de transaccin se puede logear la transaccin sin afectar la sesin principal. No utilizar COMMIT o ROLLBACK dentro de los procedimientos PL/SQL que son utilizados por procesos en otros leguajes. El COMMIT o ROLLBACK debe utilizarse dentro del proceso que llama los procedimientos PL/SQL. Cuando los procedimientos PL/SQL son llamados por otros lenguajes de programacin (Java, Visual Basic, etc.) no deben utilizar las sentencias de manejo de transacciones COMMIT o ROLLBACK de modo de posibilitar un ordenado control de transacciones.

Usar COUNT solo cuando es necesario saber el nmero de ocurrencias.

No usar COUNT para saber si hay una o ms de una fila para una condicin dada. Solo se debe usar COUNT para saber cuantas filas hay.
Ejemplo FUNCTION existe_filas( p_tabla IN VARCHAR2, p_condicion IN VARCHAR2 DEFAULT NULL, p_cant_reg IN NUMBER DEFAULT 1) RETURN BOOLEAN IS retval BOOLEAN := FALSE; l_registros NUMBER; l_cadena VARCHAR2(500); BEGIN BEGIN l_cadena := 'SELECT count(1) FROM '||p_tabla ||' WHERE '; IF p_condicion IS NOT NULL THEN l_cadena := l_cadena ||p_condicion ||' AND '; END IF; EXECUTE INMEDIATE l_cadena||' ROWNUM < '||TO_CHAR(p_cant_reg + 1) INTO l_registros; IF l_registros >= p_cant_registros THEN retval :=TRUE; END IF; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('error '||SQLERRM); END; RETURN retval; END existe_filas;

Esta forma permite tener una funcin genrica que evita recorrer toda la tabla y evita problemas en caso de ser una tabla muy grande.

Usar cursores implcitos para realizar operaciones sobre muchos registros

Este constructor realiza la apertura y cierre en forma automtica. Es fcil de usar y su lectura es ms simple
Ejemplo DECLARE CURSOR c_cuentas IS SELECT nro_cuenta, fec_alta,

BEGIN FOR r_cuentas IN c_cuentas LOOP pl (r_cuentas.nro_cuenta || ': ' ||r_cuentas.nom_apellido); END LOOP; END;

nom_apellido FROM usrdemo.cuentas WHERE titular = TRUE;

Nunca usar cursores implcitos para una fila

El FOR LOOP est diseado para mltiples filas de un cursor. Para un solo registro es ms eficiente un SELECT INTO o un cursor explcito. Parametrizar cursores explcitos

Los cursores retornan informacin como lo hara una funcin, por lo cual tambin pueden aceptar parmetros. Si se define un cursor con parmetros, se puede hacer que sea reutilizable en diferentes circunstancias de un programa. Esto es mejor si se define en un paquete.
Ejemplo CREATE OR REPLACE PACKAGE pkg_cta IS CURSOR c_titular (p_cuenta IN cuentas.nro_cuenta%TYPE) IS SELECT nom_nombre, nom_apellido FROM usrdemo.personas per WHERE EXISTS ( SELECT 1 FROM usrdemo.titular tit WHERE tit.per_id = per.per_id AND tit.nro_cuenta = p_cuenta);

Y se invoca de la siguiente forma:


BEGIN OPEN pkg_cta.c_titular(10);

Usar RETURNING para obtener informacin sobre filas modificadas

Esta clusula permite obtener informacin de las filas que se modificaron por INSERT, UPDATE o DELETE.
Ejemplo INSERT INTO usrdemo.personas ( per_id, nombre, apellido ) VALUES ( per_id.NEXTVAL, p_nombre, p_apellido); SELECT INTO FROM WHERE per_id l_per_id usrdemo.personas apellido = p_apellido;

Con RETURNING se puede realizar las dos sentencias en una


INSERT INTO usrdemo.personas ( per_id, nombre, apellido ) VALUES ( per_id.NEXTVAL, p_nombre, p_apellido) RETURNING per_id INTO l_per_id;

Esto mejora notablemente la performance en las aplicaciones y el cdigo se reduce notablemente. Referenciar atributos del cursor inmediatamente despus de ejecutar la operacin de SQL.

Las sentencias INSERT, UPDATE y DELETE se ejecutan como cursores implcitos en PL/SQL. Es decir, no se puede declarar, abrir y procesar este tipo de operaciones en forma explcita. El motor de Oracle SQL toma el control y administracin del cursor. Se puede obtener informacin acerca de los resultados de estas operaciones implcitas ms recientemente utilizada consultando los siguientes atributos de cursor. SQL%ROWCOUNT: Nmeros de filas afectadas por la instruccin DML. SQL%ISOPEN: Siempre es falso en los manejos de cursores implcitos. SQL%FOUND: Retorna TRUE si la operacin afecta al menos una fila. SQL%NOTFOUND: Retorna FALSE si la operacin afecta al menos una fila. Hay un solo set de atributos SQL% en una sesin, el cual refleja la ltima operacin implcita realizada. Por lo cual el uso de estos atributos deber hacerse con el mnimo

uso de cdigo entre la operacin y la referencia al atributo. De otra manera el valor retornado por el atributo podra no corresponder al SQL deseado.

Usar BULK COLLECT para mejorar la performance de los querys multiregistros.

En algunas oportunidades es necesario recuperar un gran nmero de filas desde la base de datos. BULK COLLECT permite obtener el set de filas en una sola llamada al motor y recuperar el resultado en un arreglo (COLLECTION). Para usar este mtodo es necesario declarar un arreglo para mantener los datos recuperados y luego anteponer al INTO la clusula BULK COLLECT.
Ejemplo PROCEDURE procesar_clientes(p_sucursal_id IN sucurales.sucursal_id%TYPE) RETURN t_clientes IS TYPE t_nro_cliente IS TABLE OF clientes.nro_cliente%TYPE; TYPE t_nombre IS TABLE OF clientes.nombre%TYPE; TYPE t_fecha IS TABLE OF clientes.fecha_alta%TYPE; l_nro_clientes t_nro_cliente; l_nombres t_nombre; l_fechas t_fecha; BEGIN SELECT nro_cliente, nombre, fecha_alta BULK COLLECT INTO l_nro_clientes, l_nombres, l_fechas FROM usrdemo.clientes WHERE sucursal_id = p_sucursal_id; ... END procesar_clientes;

Esta forma de recuperacin de datos mejora (en algunos casos enormemente) la performance del query. Se debe tener cuidado cuando el SELECT retorna miles de filas ya que si el mismo programa est corriendo con diferentes usuarios en una misma instancia, puede haber problemas de memoria. Se podra restringir usando ROWNUM o LIMIT para cada instancia.

Usar FORALL para mejorar la performance de DML basados en arreglos.

Para los casos en que es necesario modificar (INSERT, DELETE o UPDATE) un gran nmero de filas en una base de datos dentro de un PL/SQL, se recomienda la sentencia FORALL. La performance mejora notablemente ya que se reduce el nmero de llamadas entre PL/SQL y el SQL engine.
Ejemplo

Uso individual de update dentro de un for


BEGIN FOR r_cuentas IN pkg_ctas.c_cuentas LOOP UPDATE usrdemo.prestamos SET fecha_alta = SYSDATE, Cliente_id = r_cuentas.cliente_id WHERE cuenta_debito = r_cuentas.cuenta_id; END LOOP; END; Uso individual de update dentro de un for Ahora el mismo ejemplo implementado con FORALL. Realiza los mismo, pero con una sola llamada al SQL engine. (Versin para 8i) DECLARE TYPE t_cliente IS TABLE OF cuentas.cliente_id%TYPE; l_clientes t_cliente := t_cliente(); TYPE t_cuenta IS TABLE OF cuentas.cuenta_id%TYPE; l_cuentas t_cuenta := t_cuenta(); BEGIN FOR r_cuentas IN pkg_ctas.c_cuentas LOOP l_clientes.EXTEND; l_cuentas.EXTEND; l_clientes(l_cliente.LAST):= rec_cuentas.cliente_id; l_cuentas(l_cuentas.LAST) := rec_cuentas.cuenta_id; END LOOP FORALL indx IN l_clientes.FIRST .. l_clientes.LAST UPDATE usrdemo.prestamos SET fecha_alta = SYSDATE, cliente_id = l_clientes(indx) WHERE cuenta_debito = l_cuentas(indx); END;

Como se observa es necesario un loop para cargar el arreglo, sin embargo la reduccin de llamadas al SQL Engine reduce notablemente los tiempos. Para versiones 9i existen mejoras para este tipo de cdigo y para el manejo de excepciones.

7. Reglas generales para performance Para el manejo de datos dentro de un cdigo PL/SQL es necesario tener en cuenta las siguientes premisas.

Una sentencia SQL es ms rpida que un programa.

Siempre es preferible utilizar la potencia del SQL antes que emular su funcionamiento con un PL/SQL largo y complejo.

Un programa pequeo es ms rpido que uno extenso.

El uso de programas largos agrega ms tiempo de ejecucin. El uso de paquetes pequeos permite al servidor disponer de ellos de manera mas rpida y eficiente Evitar llamadas repetitivas al sql engine

Mientras menos solicitudes de servicios se hagan al sql engine, ms rpido se ejecutar un programa. Para el caso de operaciones de SQL similares o repetitivas, utilizar FORALL, BULKS, variables BIND, etc.

Los queries deben ser optimizados

Es conveniente al escribir un query evaluar su explain plan. En caso de desconocer como mejorar la performance del mismo, consultar con algn dba para solicitar ayuda.

Utilizar los features de performance de SQL y de PL/SQL de las versiones que se estn utilizando solamente para manejo de grandes volmenes de dato o cuando la aplicacin lo justifique.

La evolucin de las versiones generalmente vienen acompaadas de nuevos mecanismos para mejorar la performance para grandes cantidades de datos, sin embargo, a veces su utilizacin requiere cdigo extenso y complejo y los resultados en volmenes pequeos no reflejan el esfuerzo invertido.

Aprender los features de cada versin

Es bueno conocer las mejoras existentes, ya que cada versin trae operaciones que disminuyen el costo de escritura o brindan funcionalidades que reducen el tiempo de desarrollo y mejoran la performance.