t09 Oracle PL SQL
t09 Oracle PL SQL
Pgina 1 de 32
Introduccin.
El trabajo realizado hasta ahora con la base de datos se ha hecho de forma interactiva: el usuario introduca una orden (en SQL) y Oracle proporcionaba una respuesta. Esta forma de trabajar no resulta operativa en un entorno de produccin, porque todos los usuarios no conocen ni utilizan SQL, y adems suelen producirse frecuentes errores. Para superar estas limitaciones, Oracle incorpora un gestor PL/SQL en el servidor de la BD y en algunas de sus herramientas (Forms, Reports, Graphics, etc.). Este lenguaje incorpora todas las caractersticas propias de los lenguajes de tercera generacin: manejo de variables, estructura modular (procedimientos y funciones), estructuras de control (bifurcaciones, bucles y dems estructuras), control de excepciones, y una total integracin en el entorno Oracle. Los programas creados con PL/SQL se pueden almacenar en la base de datos como un objeto ms de sta; as se facilita a todos los usuarios autorizados el acceso a estos programas, y en consecuencia, la distribucin, instalacin y mantenimiento de software. Adems, los programas se ejecutan en el servidor, suponiendo un significativo ahorro de recursos en los clientes y de disminucin del trfico de red. El uso del lenguaje PL/SQL es tambin imprescindible para construir disparadores de bases de datos, que permiten implementar reglas complejas de negocio y auditoria en la BD. PL/SQL soporta todos los comandos de consulta y manipulacin de datos, aportando sobre SQL las estructuras de control y otros elementos propios de los lenguajes procedimentales de tercera generacin. Su unidad de trabajo es el bloque, formado por un conjunto de declaraciones, instrucciones y mecanismos de gestin de errores y excepciones.
Transacciones.
Oracle es un sistema de base de datos puramente transaccional, de tal forma, que la instruccin BEGIN TRANSACTION no existe. Una transaccin es un conjunto de sentencias SQL que se ejecutan en una base de datos como una nica operacin, confirmndose o deshacindose todo el conjunto de sentencias SQL. La transaccin puede quedar finalizada (con las sentencias apropiadas) o implcitamente (terminando la sesin). Durante la transaccin, todas las modificaciones que hagamos sobre base de datos, no son definitivas, ms concretamente, se realizan sobre un tablespace especial que se denomina tablespace de ROLLBACK, o RBS (RollBack Segment). Este tablespace tiene reservado un espacio para cada sesin activa en el servidor, y es en ese espacio donde se almacenan todas las modificaciones de cada transaccin. Una vez que la transaccin se ha finalizado, las modificaciones temporales almacenadas en el RBS, se vuelcan al tablespace original, donde est almacenada nuestra tabla. Esto permite que ciertas modificaciones que se realizan en varias sentencias, se puedan validar todas a la vez, o rechazar todas a la vez. Dentro de una transaccin se pueden crear los llamados punto de control mediante la sentencia:
SAVEPOINT Nombre_punto_control
Pgina 2 de 32
Las sentencias de finalizacin de transaccin son: COMMIT: la transaccin termina correctamente, se vuelcan los datos al tablespace original y se vaca el RBS. ROLLBACK: se rechaza la transaccin y el vaca el RBS. Cualquier cambio realizado desde que se inici la transaccin se deshace, quedando la base de datos en el mismo estado que antes de iniciarse la transaccin.
A la hora de hacer un ROLLBACK o un COMMIT se podr hacer hasta cierto punto con la sintaxis:
COMMIT TO punto_control; ROLLBACK TO punto_control;
Cuando tenemos abierta una sesin (WorkSheet), los cambios que realizamos no son visibles a otra sesin hasta que no hagamos un COMMIT. Este se puede realizar de forma manual, ejecutando el comando COMMIT; o bien, de forma automtica, cuando cerramos la sesin. En una transaccin los datos modificados no son visibles por el resto de usuarios hasta que se confirme la transaccin. Si alguna de las tablas afectadas por la transaccin tiene triggers, las operaciones que realiza el trigger estn dentro del mbito de la transaccin, y son confirmadas o deshechas conjuntamente con la transaccin. Durante la ejecucin de una transaccin, una segunda transaccin no podr ver los cambios realizados por la primera transaccin hasta que esta se confirme. El siguiente ejemplo muestra una supuesta transaccin bancaria:
DECLARE importe NUMBER; ctaOrigen VARCHAR2(23); ctaDestino VARCHAR2(23); BEGIN importe := 100; ctaOrigen := '2530 10 2000 1234567890'; ctaDestino := '2532 10 2010 0987654321'; UPDATE CUENTAS SET SALDO = SALDO importe WHERE CUENTA = ctaOrigen; UPDATE CUENTAS SET SALDO = SALDO + importe WHERE CUENTA = ctaDestino; INSERT INTO MOVIMIENTOS VALUES (ctaOrigen, ctaDestino, importe*(-1), SYSDATE); INSERT INTO MOVIMIENTOS VALUES (ctaDestino,ctaOrigen, importe, SYSDATE); COMMIT; EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Error en la transaccion:'||SQLERRM); dbms_output.put_line('Se deshacen las modificaciones); ROLLBACK; END;
Pgina 3 de 32
Programacin PL/SQL.
SQL es un lenguaje de consulta para los sistemas de bases de datos relacinales, pero que no posee la potencia de los lenguajes de programacin. PL/SQL (Procedural Language/Structured Query Language) apareci por primera vez en la versin 6 de Oracle (1988) y amplia SQL con los elementos caractersticos de los lenguajes de programacin: variables, sentencias de control de flujo, bucles... Cuando se desea realizar una aplicacin completa para el manejo de una base de datos relacional, resulta necesario utilizar alguna herramienta que soporte la capacidad de consulta del SQL y la versatilidad de los lenguajes de programacin tradicionales. PL/SQL es el lenguaje de programacin que proporciona Oracle para extender el SQL estndar con otro tipo de instrucciones. Para poder trabajar necesitaremos tener los siguientes elementos:
Una instancia de ORACLE o superior funcionando correctamente. Herramientas cliente de ORACLE, (WorkSheet o SQL*Plus) para poder ejecutar los ejemplos. Haber configurado correctamente una conexin a ORACLE.
Con PL/SQL vamos a poder programar las unidades de programa de la base de datos ORACLE, estn son:
Pero adems PL/SQL nos permite realizar programas sobre las siguientes herramientas de ORACLE:
Fundamentos de PL/SQL
Para programar en PL/SQL es necesario conocer sus fundamentos. Como introduccin vamos a ver algunos elementos y conceptos bsicos del lenguaje.
PL/SQL no es CASE-SENSITIVE, es decir, no diferencia maysculas de minsculas como otros lenguajes de programacin como C o Java. Sin embargo debemos recordar que ORACLE es CASE-SENSITIVE en la bsqueda de texto. Una lnea en PL/SQL contiene grupos de caracteres conocidos como UNIDADES LEXICAS, que pueden ser clasificadas como: DELIMITADOR: Es un smbolo simple o compuesto que tiene una funcin especial en PL/SQL. Estos pueden ser: o o Operadores Aritmticos Operadores Lgicos
Pgina 4 de 32
Operadores Relacinales
IDENTIFICADOR: Se emplean para dar nombre a los objetos PL/SQL, tales como variables, cursores, tipos y subprogramas. Los identificadores constan de una letra, seguida por una secuencia opcional de caracteres, que pueden incluir letras, nmeros, signos de dlar ($), caracteres de subrayado y smbolos de almohadilla (#). Los dems caracteres no pueden emplearse. La longitud mxima de un identificador es de 30 caracteres y todos los caracteres son significativos. LITERAL: Es un valor de tipo numrico, carcter, cadena o lgico no representado por un identificador (es un valor explcito). COMENTARIO: Es una aclaracin que el programador incluye en el cdigo. Son soportados 2 estilos de comentarios, el de lnea simple y de multilnea, para lo cual son empleados ciertos caracters especiales como son:
-- Linea simple /* Conjunto de Lneas */
Cuando se escribe cdigo en PL/SQL, este puede estar agrupado en unidades denominadas conjunto de instrucciones. Un conjunto de instrucciones puede contener otros subconjuntos y as sucesivamente. Un conjunto de instrucciones queda delimitado por las palabras reservadas BEGIN y END.
BEGIN Sentencias . . . Sentencias . . . BEGIN Sentencias . . . Sentencias . . . Sentencias . . . END; Sentencias . . . Sentencias . . . END;
Bloques PL/SQL
Los bloques PL/SQL son de varios tipos: Annimos (Anonymous blocks). Se construyen de forma dinmica y se ejecutan una sola vez. Con nombre (Named blocks). Son bloques con nombre, que al igual que el anterior se construyen, generalmente, de forma dinmica y se ejecutan una sola vez. Subprogramas. Procedimientos, paquetes o funciones almacenados en la BD. No suelen cambiar despus de su construccin y se ejecutan mltiples veces mediante una llamada call. Disparadores(Triggers). Son bloques con nombre que tambin se almacenan en la BD. Tampoco suelen cambiar despus de su construccin y se ejecutan varias veces. Se ejecutan de forma automtica ante algn suceso de disparo, que ser una orden del lenguaje de
Pgina 5 de 32
manipulacin de datos (INSERT, UPDATE o DELETE) que se ejecuta sobre una tabla de la BD. Los bloques PL/SQL presentan una estructura especfica compuesta de tres partes bien diferenciadas:
La seccin declarativa en donde se declaran todas las constantes y variables que se van a utilizar en la ejecucin del bloque. Esta seccin es opcional. La seccin de ejecucin que incluye las instrucciones a ejecutar en el bloque PL/SQL. Estas instrucciones pueden ser tanto de tipo DML como DDL, as como ordenes procedimentales. Esta es la nica seccin que es obligatoria. La seccin de excepciones en donde se definen los manejadores de errores que soportar el bloque PL/SQL. Esta seccin es opcional y no se ejecutar a menos que aparezca un error.
Cada una de las partes anteriores se delimita por una palabra reservada, de modo que un bloque PL/SQL se puede representar como sigue:
[DECLARE Declaracin de variables] /*Parte declarativa*/ BEGIN Sentencias SQL y PL/SQL /*Parte de ejecucion*/ [EXCEPTION Manejadores de excepciones] /*Parte de excepciones*/ END;
Para que la salida pueda verse al ejecutar el programa tiene que estar activa la siguiente variable:
SET SEVEROUTPUT ON;
Las variables deben declararse dentro de la seccin DECLARE y deben seguir la siguiente sintaxis:
Nombre_variable [CONSTANT] TIPO [NOT NULL] [:= inicializacin];
Pgina 6 de 32
Cualquier variable que se declare y no se inicialice tiene por defecto el valor NULL. Los tipos posibles son todos aquellos vlidos para SQL aadiendo algunos propios de PL/SQL. Para ms informacin sobre los tipos propios de PL/SQL consultar el PL/SQL Users Guide and Referente. Podemos hacer que una variable nunca tome valores nulos utilizando la clusula NOT NULL, en este caso, hay que inicializar la variable. La declaracin de una constante es similar a la declaracin de una variable, aadiendo la palabra CONSTANT y asignndole a continuacin un valor a la constante. Ejemplos:
Interes NUMBER(5,3); Descripcion VARCHAR2(50) := inicial; Fecha_max DATE; Contabilizado BOOLEAN := TRUE; PI CONSTANT REAL := 3.14159
Otra forma de asignarle un valor a una variable es mediante la clausula INTO de la sentencia SELECT:
SELECT COUNT(*) INTO xNumFac FROM FACTURAS;
Se puede declarar el tipo de una variable tomndolo de otro identificador, usando el atributo %TYPE y se puede declarar el tipo de una variable tambin cuando es un tipo estructurado con el atributo %ROWTYPE. Esto es particularmente til cuando una variable va a tomar valores de una columna de una tabla. Declarar variables con el atributo %TYPE tiene dos ventajas. Primero, no necesitamos conocer el tipo exacto de la columna de la tabla. Segundo, si cambiamos la definicin y/o tipo de la columna de la tabla, el tipo de la variable cambia automticamente en tiempo de ejecucin. En la declaracin: si tenemos una variable y por ejemplo, y est declarada de tipo char podemos declarar otra variable j de la siguiente forma:
J y%type;
Lo mismo ocurrira para declarar una estructura que ya esta declarada como por ejemplo una tabla que ya tenemos declarada. Ejemplo:
J employee%rowtype J tendra la misma estructura que la tabla employee.
En este caso para acceder a cada campo que tuviera el tabla employee mediante la variable J tendramos que usar la estructura variable.nombre_campo. Un bloque tiene acceso a los objetos identificados dentro de su esquema. Solo podremos acceder a los objetos del usuario donde estemos conectados y a los que ese usuario pueda acceder porque le hayan otorgado permisos.
Pgina 7 de 32
Como PL/SQL es un lenguaje 3GL, cuenta con las estructuras tpicas de control de flujo: bifurcaciones condicionales y bucles:
Bifurcaciones condicionales:
Como en cualquier lenguaje de programacin, condicin es cualquier expresin que de cmo resultado un valor booleano. Hay que saber que las estructuras IF se pueden anidar unas dentro de otras.
a. IF THEN
Se evala la condicin y si resulta verdadera, se ejecutan uno o ms lneas de cdigo de programa. En el caso de que la condicin resulte falsa o nula, NO se realiza NINGUNA accin.
IF fecha_nac < 1-01-1970 THEN Salario := salario *1.15; END IF; --No termina con un ; --aumento de salario en un 15%
Se evala la condicin y si resulta verdadera, se ejecutan uno o ms lneas de cdigo de programa. En el caso de que la condicin resulte falsa, se ejecutan las instrucciones que siguen a la instruccin ELSE. Slo se permite una instruccin ELSE en cada instruccin IF.
IF fecha_nac <1-01-1970 THEN salario:= salario *1.15; ELSE salario:= salario* 1.05; END IF; --No termina con un ; --aumento de salario en un 15% --No termina con un ; --aumento de salario en un 5%
Pgina 8 de 32
Se evala la condicin y si resulta verdadera, se ejecutan uno o ms lneas de cdigo de programa. En el caso de que la condicin resulte ser falsa, se evala la condicin especificada en el ELSIF.
IF apellido =Prez THEN salario:= salario *1.10; --aumento de salario en un 10% ELSIF apellido =Martnez THEN salario:= salario *1.15; --aumento de salario en un 15% ELSIF apellido=Alvarez THEN salario:= salario *1.20; --aumento de salario en un 20% ELSE salario:= salario* 1.05; --aumento de salario en un 5% END IF; --Slo se necesita un nico END IF
b. CASE La instruccin CASE puede evaluar mltiples expresiones y devolver para cada una de ellas un valor/bloque de instrucciones. El resultado de cada WHEN puede ser un valor o una sentencia; en el primer caso el resultado de una sentencia CASE se puede guardar en una variable. Su sintaxis es:
CASE variable WHEN expresin1 THEN valor1/bloque de instrucciones WHEN expresin2 THEN valor2/bloque de instrucciones WHEN expresin3 THEN valor3/bloque de instrucciones WHEN expresin4 THEN valor4/bloque de instrucciones ELSE valor5/bloque de instrucciones END
Ejemplos:
CREATE TABLE C2 ( Nombre VARCHAR2(20 ), EC VARCHAR2(1) ); COMMIT; INSERT INTO C2 ( NOMBRE, EC ) VALUES ('Juan ', 'S'); INSERT INTO C2 ( NOMBRE, EC ) VALUES ('Maria', 'C'); INSERT INTO C2 ( NOMBRE, EC ) VALUES ('Ana', 'D'); INSERT INTO C2 ( NOMBRE, EC ) VALUES ('Luis', 'S'); INSERT INTO C2 ( NOMBRE, EC ) VALUES ('Pepe', NULL); COMMIT;
Pgina 9 de 32
SELECT Nombre, CASE EC WHEN 'C' THEN 'Casado/a' WHEN 'S' THEN 'Soltero/a' WHEN 'D' THEN 'Divorciado/a' ELSE 'Otros' END AS "Estado Civil" FROM C2;
En esta sintaxis despus del CASE no aparece ninguna variable y cada WHEN tiene su propia condicin a evaluar.
Bucles LOOP sentencias END LOOP;
Las sentencias de dentro del bucle se ejecutarn durante un nmero indefinido de vueltas, hasta que aparezca la instruccin EXIT; que finalizar el bucle. Este tipo de bucle se denomina bucle incondicional.
LOOP Sentencias IF (expresion) THEN Sentencias EXIT; END IF; END LOOP;
Otra opcin es incluir la estructura EXIT WHEN condicin, se terminar el bucle cuando la condicin se cumpla:
LOOP Sentencias EXIT WHEN condicin; Sentencias END LOOP;
Ejemplo
DECLARE -- declare and assign values to variables total NUMBER(9) := 0; counter NUMBER(6) := 0; BEGIN LOOP
Tema 9. PL/SQL de ORACLE. counter := counter + 1; -- increment counter variable total := total + counter * counter; -- compute total -- exit loop when condition is true EXIT WHEN total > 25000; -- LOOP until condition is met END LOOP; DBMS_OUTPUT.PUT_LINE('Counter: ' || TO_CHAR(counter) || ' Total: ' || TO_CHAR(total)); END;
Pgina 10 de 32
Ejemplo
DECLARE i NUMBER := 1; i_cubed NUMBER; BEGIN WHILE i <= 10 LOOP i_cubed := i**3; DBMS_OUTPUT.PUT_LINE('Number: ' || TO_CHAR(i) || ' Cube: ' || TO_CHAR(i_cubed)); i := i + 1; END LOOP; END;
En los bucles WHILE tambin se pueden utilizar las rdenes EXIT o EXIT WHEN para salirnos sin esperar a que la condicin devuelva un valor falso. Y por ltimo el bucle FOR:
FOR contador IN [REVERSE] limite_inferior..limite_superior LOOP sentencias END LOOP; Contador deber ser una variable de tipo numrico que sea capaz de contener los valores comprendidos entre limite_inferior y limite_superior, los cuales debern ser expresiones numricas, ya sean constantes (1,10) o funciones (ROUND(max,0), ASCII(A)) .
Si la variable contador no est definida, PL/SQL definir una variable de tipo INTEGER al iniciar el bucle, y la liberar al finalizar el bucle.
SET SERVEROUTPUT ON; BEGIN FOR loop_counter IN 1..10 LOOP DBMS_OUTPUT.PUT_LINE('Number: ' || TO_CHAR(loop_counter) || ' Square: ' || TO_CHAR(loop_counter**2)); END LOOP; END;
Pgina 11 de 32
Anteriormente dijimos que un bloque de cdigo puede contener una seccin denominada EXCEPTION. Esta seccin es la encargada de recoger todas las anomalas que se puedan producir dentro del bloque de cdigo. Una excepcin es una situacin especial dentro de la ejecucin de un programa, que puede ser capturada para asignar un nuevo comportamiento. Una excepcin puede ser un error de ejecucin (una divisin entre 0) o cualquier otro tipo de suceso. Las excepciones deben ser declaradas dentro de la seccin DECLARE, como si de una variable se tratase:
DECLARE e_sin_alumnos EXCEPTION;
Una vez que la excepcin est definida, sta debe ser lanzada, ya sea automticamente por Oracle ( cuando se produce un error controlado por Oracle ), o lanzada manualmente por el usuario a travs de la instruccin RAISE <excepcin>. La sintaxis del manejador de excepciones es:
EXCEPTION WHEN nb_excepcion_1 THEN instrucciones excep1; WHEN nb_excepcion_2 THEN instrucciones excep2; ... WHEN nb_excepcion_n THEN instrucciones excepn; [WHEN OTHERS THEN instrucciones;]
Ejemplo:
DECLARE VALOR_NEGATIVO EXCEPTION; valor NUMBER; BEGIN valor := -1; IF valor < 0 THEN RAISE VALOR_NEGATIVO; END IF; EXCEPTION WHEN VALOR_NEGATIVO THEN dbms_output.put_line('El valor no puede ser negativo'); END;
Cuando se produce un error, se ejecuta el bloque EXCEPTION. Si existe un bloque de excepcin apropiado para el tipo de error producido se ejecuta dicho bloque. Si este ltimo no existe, se ejecutar el bloque de excepcin WHEN OTHERS THEN ( en el caso de haberlo definido; este bloque debe ser el ltimo manejador de excepciones ). Una vez finalizada la ejecucin del bloque de EXCEPTION no se contina ejecutando el bloque anterior.
Pgina 12 de 32
En ocasiones queremos enviar un mensaje de error personalizado al producirse una excepcin PL/SQL. Para ello es necesario utilizar la instruccion RAISE_APPLICATION_ERROR;
RAISE_APPLICATION_ERROR(<error_num>,<mensaje>);
donde:
error_num es un entero negativo comprendido entre -20001 y -20999 mensaje la descripcin del error
DECLARE v_div NUMBER; BEGIN SELECT 1/0 INTO v_div FROM DUAL; EXCEPTION WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20001,'No se puede dividir por cero'); END;
Dentro del bloque de excepciones conviene recordar la existencia de la excepcin OTHERS, que simboliza cualquier condicin de excepcin que no ha sido declarada. Se utiliza comnmente para controlar cualquier tipo de error que no ha sido previsto. En ese caso, es comn observar la sentencia ROLLBACK en el grupo de sentencias de la excepcin o alguna de las funciones SQLCODE SQLERRM. SQLCODE devuelve el nmero del error de Oracle y un 0 (cero) en caso de xito al ejecutarse una sentencia SQL. SQLERRM devuelve el la descripcin del correspondiente mensaje de error. Tambin es posible entregarle a la funcin SQLERRM un nmero negativo que represente un error de Oracle y sta devolver el mensaje asociado. 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.
SET SERVEROUTPUT ON;
DECLARE err_num NUMBER; err_msg VARCHAR2(255); result NUMBER; msg VARCHAR2(255); BEGIN msg := SQLERRM(-1403); DBMS_OUTPUT.put_line(MSG); 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;
Tema 9. PL/SQL de ORACLE. DECLARE e_sinreg EXCEPTION; a number(10) := 25; b number(10) := 0; c number(10); BEGIN Select count(*) INTO a FROM Articulos; If a < 10 THEN RAISE e_sinreg; END IF; c := a / b; DBMS_OUTPUT.PUT_LINE(' Esto nunca llegar a mostrarse. ');
Pgina 13 de 32
EXCEPTION WHEN ZERO_DIVIDE THEN DBMS_OUTPUT.PUT_LINE('No se puede dividir por 0'); WHEN e_sinreg THEN DBMS_OUTPUT.PUT_LINE('Hay menos de 10 articulos.'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Se ha producido otra excepcin.'); END;
Las lneas de cdigo debajo del manejador especfico se ejecutarn cuando esa excepcin se produzca. Algunas excepciones se lanzarn automticamente cuando se produzcan ciertos tipos de errores en la ejecucin del bloque de cdigo. Cada excepcin automtica tiene asociado un cdigo de error ORA-XXXX el cual si se produce, har que se lance la excepcin correspondiente. A continuacin se muestra una lista de las excepciones automticas predefinidas por Oracle:
Pgina 14 de 32
Un cursor es el nombre para un rea memoria privada que contiene informacin procedente de la ejecucin de una sentencia SELECT. Cada cursor tiene unos atributos que nos devuelven informacin til sobre el estado del cursor en la ejecucin de la sentencia SQL. Cuando un cursor est abierto y los datos referenciados por la consulta SELECT cambian, estos cambios no son recogidos por el cursor. PL/SQL crea implcitamente un cursor para todas las sentencias SQL de manipulacin de datos sobre un conjunto de filas, incluyendo aquellas que solo devuelven una sola fila. En PL/SQL no se pueden utilizar sentencias SELECT de sintaxis bsica ( SELECT <lista> FROM <tabla(s)> ). PL/SQL utiliza cursores para gestionar las instrucciones SELECT. Un cursor es un conjunto de registros devuelto por una instruccin SQL. Podemos distinguir dos tipos de cursores:
Cursores implcitos. Este tipo de cursores se utiliza para operaciones SELECT INTO. Se usan cuando la consulta devuelve un nico registro. Cursores explcitos. Son los cursores que son declarados y controlados por el programador. Se utilizan cuando la consulta devuelve un conjunto de registros. Ocasionalmente tambin se utilizan en consultas que devuelven un nico registro por razones de eficiencia. Son ms rpidos.
Un cursor se define como cualquier otra variable de PL/SQL y debe nombrarse de acuerdo a los mismos convenios que cualquier otra variable. Los cursores implcitos se utilizan para realizar consultas SELECT que devuelven un nico registro. Deben tenerse en cuenta los siguientes puntos cuando se utilizan cursores implcitos:
Los cursores implcitos no deben ser declarados Con cada cursor implcito debe existir la palabra clave INTO. Las variables que reciben los datos devueltos por el cursor tienen que contener el mismo tipo de dato que las columnas de la tabla. Los cursores implcitos solo pueden devolver una nica fila. En caso de que se devuelva ms de una fila (o ninguna fila) se producir una excepcin. Las ms comunes son :
Excepcin
pero ninguna fila satisface sus condiciones. Es decir, cuando "no hay datos"
TOO_MANY_ROWS Dado que cada cursor implcito slo es capaz de recuperar una fila ,
SET SERVEROUTPUT ON; declare vdescripcion VARCHAR2(50); begin SELECT DESCRIPCION INTO vdescripcion from PAISES WHERE CO_PAIS = 'ESP'; dbms_output.put_line('La lectura del cursor es: ' || vdescripcion); end;
Pgina 15 de 32
Para procesar instrucciones SELECT que devuelvan ms de una fila, son necesarios cursores explcitos combinados con una estructura de bloque. A partir de ahora, cuando hagamos referencia a cursores nos referiremos a cursores explcitos. Para trabajar con un cursor hay que realizar los siguientes pasos: 1.- Declarar el cursor 2.- Abrir el cursor en el servidor 3.- Recuperar cada una de sus filas (bucle) 4.- Cerrar el cursor
1. Declarar el cursor
Al igual que cualquier otra variable, el cursor se declara en la seccin DECLARE. Se define el nombre que tendr el cursor y qu consulta SELECT ejecutar. No es ms que una declaracin. La sintaxis bsica es:
CURSOR nombre_cursor IS instruccin_SELECT CURSOR nombre_cursor(param1 tipo1, ..., paramN tipoN) IS instruccin_SELECT
Una vez que el cursor est declarado ya podr ser utilizado dentro del bloque de cdigo. Antes de utilizar un cursor se debe abrir. En ese momento se ejecuta la sentencia SELECT asociada y se almacena el resultado en el rea de contexto (estructura interna de memoria que maneja el cursor). Un puntero seala a la primera fila
2. Abrir el cursor
Al abrir el cursor se ejecuta la sentencia SELECT asociada y cuyo resultado se guarda en el servidor en un rea de memoria interna (tablas temporales) de las cuales se va retornando cada una de las filas segn se va pidiendo desde el cliente. Al abrir un cursor, un puntero sealar al primer registro. La sintaxis de apertura de un cursor es:
OPEN nombre_cursor; OPEN nombre_cursor(valor1, valor2, ..., valorN);
Una vez que el cursor est abierto, se podr empezar a pedir los resultados al servidor.
3. Recuperar cada una de sus filas.
Una vez que el cursor est abierto en el servidor se podr hacer la peticin de recuperacin de fila. En cada recuperacin solo se acceder a una nica fila. La sintaxis de recuperacin de fila de un cursor es:
FETCH nombre_cursor INTO variables;
Podremos recuperar filas mientras la consulta SELECT tenga filas pendientes de recuperar. Para saber cundo no hay ms filas podemos consultar los siguientes atributos de un cursor:
Pgina 16 de 32
Al recuperar un registro, la informacin recuperada se guarda en una o varias variables. Si slo se hace referencia a una variable, sta se puede declarar con %ROWTYPE. Si se utiliza una lista de variables, cada variable debe coincidir en tipo y orden con cada una de las columnas de la sentencia SELECT. As lo accin ms tpica es recuperar filas mientras queden alguna por recuperar en el servidor. Esto lo podremos hacer a travs de los siguientes bloques:
OPEN nombre_cursor; LOOP FETCH nombre_cursor INTO variables; EXIT WHEN nombre_cursor%NOTFOUND; <procesar cada una de las filas> END LOOP; OPEN nombre_cursor; FETCH nombre_cursor INTO lista_variables; WHILE nombre_cursor%FOUND LOOP /* Procesamiento de los registros recuperados */ FETCH nombre_cursor INTO lista_variables; END LOOP; CLOSE nombre_cursor;
FOR variable IN nombre_cursor LOOP /* Procesamiento de los registros recuperados */ END LOOP; 4. Cerrar el cursor
Una vez que se han recuperado todas las filas del cursor, hay que cerrarlo para que se liberen de la memoria del servidor los objetos temporales creados. Si no cerrsemos el cursor, la tabla temporal quedara en el servidor almacenada con el nombre dado al cursor y la siguiente vez ejecutsemos ese bloque de cdigo, nos dara la excepcin CURSOR_ALREADY_OPEN (cursor ya abierto) cuando intentsemos abrir el cursor. Para cerrar el cursor se utiliza la siguiente sintaxis:
CLOSE nombre_cursor;
Cuando un cursor est cerrado, no se puede leer. Cuando leemos un cursor debemos comprobar el resultado de la lectura utilizando los atributos de los cursores. Cuando se cierra el cursor, es ilegal tratar de usarlo. El nombre del cursor es un identificador, no una variable. Se utiliza para identificar la consulta, por eso no se puede utilizar en expresiones.
Atributos en cursores implcitos Los cursores implcitos no se pueden manipular por el usuario, pero Oracle s permite el uso de sus atributos. Las sentencia a travs de las que podemos obtener informacin de estos atributos son: SELECT...INTO, INSERT, UPDATE, DELETE.
Pgina 17 de 32
En este caso, se debe anteponer al nombre del atributo el prefijo SQL, en lugar del nombre del cursor. SQL%NOTFOUND devuelve TRUE cuando la ltima sentencia SELECT no recuper ninguna fila, o cuando INSERT, DELETE o UPDATE no afectan a ninguna fila SQL%FOUND devuelve TRUE cuando la ltima sentencia SELECT devuelve alguna fila, o cuando INSERT, DELETE o UPDATE afectan a alguna fila SQL%ROWCOUNT devuelve el nmero de filas afectadas por INSERT, DELETE o UPDATE o las filas devueltas por una sentencia SELECT SQL%ISOPEN siempre devuelve FALSE, porque Oracle cierra automticamente el cursor implcito cuando termina la ejecucin de la sentencia SELECT
Ejemplos:
DECLARE CURSOR cpaises IS SELECT CO_PAIS, DESCRIPCION, CONTINENTE FROM PAISES; co_pais VARCHAR2(3); descripcion VARCHAR2(50); continente VARCHAR2(25); BEGIN OPEN cpaises; FETCH cpaises INTO co_pais,descripcion,continente; DBMS_OUTPUT.PUT_LINE(continente); CLOSE cpaises; END; DECLARE CURSOR cpaises IS SELECT CO_PAIS, DESCRIPCION, CONTINENTE FROM PAISES; registro cpaises%ROWTYPE; BEGIN OPEN cpaises; FETCH cpaises INTO registro; DBMS_OUTPUT.PUT_LINE(continente); CLOSE cpaises; END; DECLARE r ARTICULOS%ROWTYPE; BEGIN FOR r IN ( SELECT * FROM ARTICULOS ) LOOP DBMS_OUTPUT.PUT_LINE(r.cArtDsc); END LOOP; END; BEGIN UPDATE ARTICULOS SET cArtDsc = `Pantalla LCD WHERE cCodArt = LCD; IF SQL%NOTFOUND THEN -- Otra opcin : SQL%ROWCOUNT = 0 INSERT INTO ARTICULOS (cCodArt,cDesArt) VALUES (LCD,Pantalla LCD); END IF; END;
Pgina 18 de 32
Los cursores son aquellos que permiten utilizar la orden OPEN para pasarle al cursor el valor de uno o varios de sus parmetros.
DECLARE CURSOR cArt (cFml Articulos.cArtFml%TYPE) IS SELECT cArtCdg,cArtDsc FROM Articulos WHERE cArtFml = cFml; xCod Articulos.cArtCdg%TYPE; xDes Articulos.cArtDsc%TYPE; BEGIN OPEN cArt('F1'); LOOP FETCH cArt INTO xCod,xDes; EXIT WHEN cArt%NOTFOUND; DBMS_OUTPUT.PUT_LINE (xDes); END LOOP; CLOSE cArt; END; Cursores de actualizacin
Los cursores de actualizacin se declaran igual que los cursores explcitos, aadiendo FOR UPDATE al final de la sentencia SELECT.
CURSOR nombre_cursor IS instruccin_SELECT FOR UPDATE
Para actualizar los datos del cursor hay que ejecutar una sentencia UPDATE especificando la clusula WHERE CURRENT OF <cursor_name>.
UPDATE <nombre_tabla> SET <campo_1> = <valor_1>[,<campo_2> = <valor_2>] WHERE CURRENT OF <cursor_name>
Cuando trabajamos con cursores de actualizacin debemos tener en cuenta que la sentencia UPDATE genera bloqueos en la base de datos ( transacciones, disparadores,etc).
DECLARE CURSOR cpaises IS select CO_PAIS, DESCRIPCION, CONTINENTE from paises FOR UPDATE; co_pais VARCHAR2(3); descripcion VARCHAR2(50); continente VARCHAR2(25); BEGIN OPEN cpaises; FETCH cpaises INTO co_pais,descripcion,continente; WHILE cpaises%found LOOP UPDATE PAISES SET CONTINENTE = CONTINENTE || '.' WHERE CURRENT OF cpaises; FETCH cpaises INTO co_pais,descripcion,continente; END LOOP; CLOSE cpaises; COMMIT; END;
Pgina 19 de 32
Una vez que tenemos escrito un bloque de cdigo, podemos guardarlo en un fichero .SQL para su posterior uso, o bien guardarlo en base de datos para que pueda ser ejecutado por cualquier aplicacin. El segundo caso se realiza mediante procedimientos almacenados (Stored Procedure). A la hora de guardar un bloque de cdigo hay que tener en cuenta ciertas normas: Palabra reservada DECLARE desaparece. Podremos crear procedimientos y funciones. Los procedimientos no podrn retornar ningn valor sobre su nombre, mientras que las funciones deben retornar un valor de un tipo de dato bsico.
Un procedimiento [almacenado] es un subprograma que ejecuta una accin especfica y que no devuelve ningn valor por si mismo, como sucede con las funciones. Un procedimiento tiene un nombre, un conjunto de parmetros (opcional) y un bloque de cdigo. Para crear un procedimiento (stored procedure: procedimiento almacenado) usaremos la siguiente sintaxis:
CREATE {OR REPLACE} PROCEDURE nombre_proc( param1 [IN | OUT | IN OUT] tipo,... ) IS -- Declaracin de variables locales BEGIN -- Instrucciones de ejecucin [EXCEPTION] -- Instrucciones de excepcin END;
Tras crear el procedimiento, ste se compila y luego se almacena en la BD de forma compilada. Este procedimiento luego puede ser invocado desde cualquier bloque PL/SQL. El uso de OR REPLACE permite sobrescribir un procedimiento existente. Si se omite, y el procedimiento existe, se producir, un error. Debemos especificar el tipo de datos de cada parmetro. Al especificar el tipo de dato del parmetro no debemos especificar la longitud del tipo, aunque si puede ser utilizando el operador %TYPE. Los parmetros pueden ser de entrada (IN), de salida (OUT) o de entrada salida (IN OUT). El valor por defecto es IN, y se toma ese valor en caso de que no especifiquemos nada.
CREATE OR REPLACE PROCEDURE Actualiza_Saldo(cuenta NUMBER, new_saldo NUMBER) IS -- Declaracion de variables locales BEGIN UPDATE SALDOS_CUENTAS SET SALDO = new_saldo, FX_ACTUALIZACION = SYSDATE WHERE CO_CUENTA = cuenta; END Actualiza_Saldo;
Tambin podemos asignar un valor por defecto a los parmetros, utilizando la clusula DEFAULT o el operador de asignacin (:=) .
CREATE OR REPLACE PROCEDURE Actualiza_Saldo(cuenta NUMBER, new_saldo NUMBER DEFAULT 10)
Pgina 20 de 32
Una vez creado y compilado el procedimiento almacenado podemos ejecutarlo. Existen dos formas de pasar argumentos a un procedimiento almacenado a la hora de ejecutarlo. Estas son:
Notacin posicional: Se pasan los valores de los parmetros en el mismo orden en que el procedure los define.
BEGIN Actualiza_Saldo(200501,2500); COMMIT; END;
Notacin nominal: Se pasan los valores en cualquier orden nombrando explcitamente el parmetro y su valor separados por el smbolo => .
BEGIN Actualiza_Saldo(cuenta => 200501,new_saldo => 2500); COMMIT; END; CREATE OR REPLACE PROCEDURE today_is AS BEGIN DBMS_OUTPUT.PUT_LINE( 'Hoy es ' || TO_CHAR(SYSDATE, ' DD/MM/YYYY') ); END today_is; / -- para ejecutarlo SET SERVEROUTPUT ON; BEGIN today_is(); -- the parentheses are optional here END; CREATE OR REPLACE PROCEDURE today2_is ( fecha DATE ) AS BEGIN DBMS_OUTPUT.PUT_LINE( 'Hoy es ' || TO_CHAR(fecha, ' DD/MM/YYYY') ); END; -- para ejecutarlo SET SERVEROUTPUT ON; BEGIN today2_is(to_date('01/02/2008')); -- the parentheses are optional here END; -- para ejecutarlo SET SERVEROUTPUT ON; BEGIN today2_is(fecha => to_date('01/02/2008')); -- the parentheses are optional here END;
Pgina 21 de 32
Packages.
Adems de brindarnos mltiples elementos que nos permiten desarrollar una aplicacin robusta, Oracle nos ofrece la posibilidad de programar en forma modular, clara y eficiente. En este apartado veremos cmo embeber procedimientos, funciones, definiciones de tipos de datos y declaraciones de variables en una misma estructura que los agrupe y relacione lgicamente. Esta estructura se denomina Package (Paquete) y su uso nos permite no slo mejorar la calidad de diseo de nuestras aplicaciones sino tambin optimizar el desempeo de las mismas. Un Paquete es un objeto PL/Sql que agrupa lgicamente otros objetos PL/Sql relacionados entre s, encapsulndolos y convirtindolos en una unidad dentro de la base de datos. Los Paquetes estn divididos en 2 partes: especificacin (obligatoria) y cuerpo (no obligatoria). La especificacin o encabezado es la interfaz entre el Paquete y las aplicaciones que lo utilizan y es all donde se declaran los tipos, variables, constantes, excepciones, cursores, procedimientos y funciones que podrn ser invocados desde fuera del paquete. En el cuerpo del paquete se implementa la especificacin del mismo. El cuerpo contiene los detalles de implementacin y declaraciones privadas, mantenindose todo esto oculto a las aplicaciones externas, siguiendo el conocido concepto de caja negra. Slo las declaraciones hechas en la especificacin del paquete son visibles y accesibles desde fuera del paquete (por otras aplicaciones o procedimientos almacenados) quedando los detalles de implementacin del cuerpo del paquete totalmente ocultos e inaccesibles para el exterior. Para acceder a los elementos declarados en un paquete basta con anteceder el nombre del objeto referenciado con el nombre del paquete donde est declarado y un punto, de esta manera: Paquete.Objeto donde Objeto puede ser un tipo, una variable, un cursor, un procedimiento o una funcin declarados dentro del paquete. Para referenciar objetos desde adentro del mismo paquete donde han sido declarados no es necesario especificar el nombre del paquete y pueden (deberan) ser referenciados directamente por su nombre. Finalmente y siguiendo a la parte declarativa del cuerpo de un paquete puede, opcionalmente, incluirse la seccin de inicializacin del paquete. En esta seccin pueden, por ejemplo, inicializarse variables que utiliza el paquete. La seccin de inicializacin se ejecuta slo la primera vez que una aplicacin referencia a un paquete, esto es, se ejecuta slo una vez por sesin. Como hemos dicho anteriormente, la creacin de un paquete pasa por dos fases: Crear la cabecera del paquete donde se definen que procedimientos, funciones, variables, cursores, etc. Disponibles para su uso posterior fuera del paquete. En esta parte solo se declaran los objetos, no se implementa el cdigo. Crear el cuerpo del paquete, donde se definen los bloques de cdigo de las funciones y procedimientos definidos en la cabecera del paquete.
CREATE {OR REPLACE} PACKAGE nombre_de_paquete IS < Declaraciones > END; Para crear el cuerpo del paquete utilizaremos la siguiente instruccin: CREATE {OR REPLACE} PACKAGE BODY nombre_paquete IS < Bloques de cdigo> END;
Pgina 22 de 32
Hay que tener en cuenta que toda declaracin de funcin o procedimiento debe estar dentro del cuerpo del paquete, y que todo bloque de cdigo contenido dentro del cuerpo debe estar declarado dentro de la cabecera de paquete. Cuando se quiera acceder a las funciones, procedimientos y variables de un paquete se debe anteponer el nombre de este: Nombre_paquete.funcin(x) Nombre_paquete.procedimiento(x) Nombre_paquete.variable
CREATE OR REPLACE PACKAGE PK1 IS PROCEDURE xLis (xfamilia IN Articulos.cArtFml%TYPE); END; CREATE OR REPLACE PACKAGE BODY PK1 IS procedure xLis (xfamilia Articulos.cArtCdg%TYPE) IS xfam Articulos.cArtFml%type; xCod Articulos.cArtCdg%TYPE; xDes Articulos.cArtDsc%TYPE; CURSOR cArticulos IS SELECT cArtCdg,cArtDsc FROM Articulos WHERE cArtFml = xfam; BEGIN xfam := xfamilia; OPEN cArticulos; LOOP FETCH cArticulos INTO xCod,xDes; EXIT WHEN cArticulos%NOTFOUND; DBMS_OUTPUT.PUT_LINE (xDes); END LOOP; CLOSE cArticulos; END; END;
Ventajas del uso de Paquetes. Dentro de las ventajas que ofrece el uso de paquetes podemos citar que: Permite modularizar el diseo de nuestra aplicacin
El uso de Paquetes permite encapsular elementos relacionados entre s (tipos, variables, procedimientos, funciones) en un nico mdulo PL/Sql que llevar un nombre que identifique la funcionalidad del conjunto. Otorga flexibilidad al momento de disear la aplicacin
En el momento de disear una aplicacin todo lo que necesitaremos inicialmente es la informacin de interfaz en la especificacin del paquete. Puede codificarse y compilarse la especificacin sin su cuerpo para posibilitar que otros sub-programas que referencian a estos elementos declarados puedan compilarse sin errores. De esta manera podremos armar un prototipo de nuestro sistema antes de codificar el detalle del mismo.
Pgina 23 de 32
Pueden especificarse cules tipos, variables y sub-programas dentro del paquete son pblicos (visibles y accesibles por otras aplicaciones y sub-programas fuera del paquete) o privados (ocultos e inaccesibles fuera del paquete). Por ejemplo, dentro del paquete pueden existir procedimientos y funciones que sern invocados por otros programas, as como tambin otras rutinas de uso interno del paquete que no tendrn posibilidad de ser accedidas fuera del mismo. Esto asegura que cualquier cambio en la definicin de estas rutinas internas afectar slo al paquete donde se encuentran, simplificando el mantenimiento y protegiendo la integridad del conjunto. Agrega mayor funcionalidad a nuestro desarrollo
Las definiciones pblicas de tipos, variables y cursores hechas en la especificacin de un paquete persisten a lo largo de una sesin. Por lo tanto pueden ser compartidas por todos los subprogramas y/o paquetes que se ejecutan en ese entorno durante esa sesin. Por ejemplo, puede utilizarse esta tcnica (en dnde slo se define una especificacin de paquete y no un cuerpo) para mantener tipos y variables globales a todo el sistema. Introduce mejoras al rendimiento
En relacin a su ejecucin, cuando un procedimiento o funcin que est definido dentro de un paquete es llamado por primera vez, todo el paquete es ingresado a memoria. Por lo tanto, posteriores llamadas al mismo u otros sub-programas dentro de ese paquete realizarn un acceso a memoria en lugar de a disco. Esto no sucede con procedimientos y funciones estndares. Permite la Sobrecarga de funciones (Overloading).
PL/SQL nos permite que varios procedimientos o funciones almacenadas, declaradas dentro de un mismo paquete, tengan el mismo nombre. Esto nos es muy til cuando necesitamos que los sub-programas puedan aceptar parmetros que contengan diferentes tipos de datos en diferentes instancias. En este caso Oracle ejecutar la versin de la funcin o procedimiento cuyo encabezado se corresponda con la lista de parmetros recibidos.
Pgina 24 de 32
Los disparadores (o triggers) son bloques de cdigo PL/SQL asociados a una tabla y que se ejecutan automticamente como reaccin a una operacin DML especfica (INSERT, UPDATE o DELETE) sobre dicha tabla. En definitiva, los disparadores son eventos a nivel de tabla que se ejecutan automticamente cuando se realizan ciertas operaciones sobre la tabla. Para crear un disparador utilizaremos la siguiente instruccin:
CREATE {OR REPLACE} TRIGGER nombre_disp [BEFORE|AFTER] [DELETE|INSERT|UPDATE {OF columnas}] [ OR [DELETE|INSERT|UPDATE {OF columnas}]...] ON tabla [FOR EACH ROW [WHEN condicion disparo]] [DECLARE] -- Declaracin de variables locales BEGIN -- Instrucciones de ejecucin [EXCEPTION] -- Instrucciones de excepcin END;
El uso de OR REPLACE permite sobrescribir un trigger existente. Si se omite, y el trigger existe, se producir, un error. En principio, dentro del cuerpo de programa del trigger podemos hacer uso de cualquier orden de consulta o manipulacin de la BD, y llamadas a funciones o procedimientos siempre que: No se utilicen comandos DDL No se acceda a las tablas que estn siendo modificadas con DELETE, INSERT o UPDATE en la misma sesin No se violen las reglas de integridad, es decir no se pueden modificar llaves primarias, ni actualizar llaves externas No se utilicen sentencias de control de transacciones (Commit, Rollback o Savepoint) No se llamen a procedimientos que trasgredan la restriccin anterior No se llame a procedimientos que utilicen sentencias de control de transacciones
Predicados condicionales.
Cuando se crea un trigger para ms de una operacin DML, se puede utilizar un predicado condicional en las sentencias que componen el trigger que indique que tipo de operacin o sentencia ha disparado el trigger. Estos predicados condicionales son los siguientes:
Inserting Deleting Updating Updating (columna)
Retorna true cuando el trigger ha sido disparado por un INSERT Retorna true cuando el trigger ha sido disparado por un DELETE Retorna true cuando el trigger ha sido disparado por un UPDATE Retorna true cuando el trigger ha sido disparado por un UPDATE y la columna ha sido modificada.
Tema 9. PL/SQL de ORACLE. CREATE TRIGGER audit_trigger BEFORE INSERT OR DELETE OR UPDATE ON classified_table FOR EACH ROW BEGIN IF INSERTING THEN INSERT INTO audit_table VALUES (USER || ' is inserting' || ' new key: ' || :new.key); ELSIF DELETING THEN INSERT INTO audit_table VALUES (USER || ' is deleting' || ' old key: ' || :old.key); ELSIF UPDATING('FORMULA') THEN INSERT INTO audit_table VALUES (USER || ' is updating' || ' old formula: ' || :old.formula || ' new formula: ' || :new.formula); ELSIF UPDATING THEN INSERT INTO audit_table VALUES (USER || ' is updating' || ' old key: ' || :old.key || ' new key: ' || :new.key); END IF; END;
Pgina 25 de 32
Tipos de triggers.
Los triggers pueden definirse para las operaciones INSERT, DELETE o Update, y pueden dispararse antes o despus de la operacin. Finalmente, el nivel de los disparadores puede ser la fila o registro o la orden. El modificador BEFORE o AFTER indica que el trigger se ejecutar antes o despus de ejecutarse la sentencia SQL definida por DELETE, INSERT o UPDATE. Si incluimos el modificador OF el trigger solo se ejecutar cuando la sentencia SQL afecte a los campos incluidos en la lista. El alcance de los disparadores puede ser la fila o de orden. El modificador FOR EACH ROW indica que el trigger se disparar cada vez que se realizan operaciones sobre cada fila de la tabla. Si se acompaa del modificador WHEN, se establece una restriccin; el trigger solo actuar, sobre las filas que satisfagan la restriccin. Tipos de disparadores.
Categoria Orden Temporalizacin Nivel Valores INSERT, DELETE, UPDATE BEFORE o AFTER Fila u Orden Comentarios
Define que tipo de operacin DML provoca la activacin del trigger Define si el disparador se activa antes o despus de que se ejecute la operacin DML Los disparadores con nivel de fila se activan una vez por cada fila afectada por la orden que provoc el disparo. Los Triggers con nivel de orden se activan slo una vez, antes o despus de la orden. Los disparadores con nivel de fila se identifican por la clusula FOR EACH ROW en la definicin del disparador.
Pgina 26 de 32
Una misma tabla puede tener varios triggers asociados. En tal caso es necesario conocer el orden en el que se van a ejecutar. Los disparadores se activan al ejecutarse la sentencia SQL. Si existe, se ejecuta el disparador de tipo BEFORE (disparador previo) con nivel de orden. 2. Para cada fila a la que afecte la orden: 2.1. Se ejecuta si existe, el disparador de tipo BEFORE con nivel de fila. 2.2. Se ejecuta la propia orden. 2.3. Se ejecuta si existe, el disparador de tipo AFTER (disparador posterior) con nivel de fila. 3. Se ejecuta, si existe, el disparador de tipo AFTER con nivel de orden.
1. Restricciones de los Triggers.
El cuerpo de un disparador es un bloque PL/SQL. Cualquier orden que sea legal en un bloque PL/SQL , es legal en el cuerpo de un disparador, con las siguientes restricciones: a. Un disparador no puede emitir ninguna orden de control de transacciones ( COMMIT, ROLLBACK o SAVEPOINT). El disparador se activa como parte de la ejecucin de la orden que provoc el disparo, y forma parte de la misma transaccin qu dicha orden. Cuando la orden que provoca la orden es confirmada o cancelada, se confirma o se cancela tambin el trabajo realizado por el disparador. b. Por las mismas razones, ningn procedure o funcin llamado por el disparador puede emitir rdenes de control de transacciones. c. El disparador no puede declarar variables de tipo LONG.
Utilizacin de :old y :new en los disparadores con nivel de fila.
Un disparador con nivel de fila se ejecuta una vez por cada fila procesada por la orden que provoca el disparo. Dentro del disparador, puede accederse a la fila que est siendo actualmente procesada utilizando, para ello, dos pseudo-registros, :old y :new. En principio tanto :old como :new son del tipo tabla_disparo%ROWTYPE;
Orden de disparo INSERT UPDATE DELETE :old :new
No definido; todos los campos toman el valor NULL. Valores originales de la fila, antes de la actualizacin. Valores originales, antes del borrado de la fila.
Valores que sern insertados cuando se complete la orden. Nuevos valores que sern escritos cuando se complete la orden. No definido; todos los campos toman el valor NULL.
Pgina 27 de 32
Ejemplo :
CREATE TRIGGER scott.emp_permit_changes BEFORE DELETE OR INSERT OR UPDATE ON scott.emp DECLARE dummy INTEGER; BEGIN /* If today is a Saturday or Sunday, then return an error.*/ IF (TO_CHAR(SYSDATE, 'DY') = 'SAT' OR TO_CHAR(SYSDATE, 'DY') = 'SUN') THEN raise_application_error( -20501, 'May not change employee table during the weekend'); END IF; /* Compare today's date with the dates of all company holidays. If today is a company holiday, then return an error.*/ SELECT COUNT(*) INTO dummy FROM company_holidays WHERE day = TRUNC(SYSDATE); IF dummy > 0 THEN raise_application_error( -20501, 'May not change employee table during a holiday'); END IF; /*If the current time is before 8:00AM or after 6:00PM, then return an error. */ IF (TO_CHAR(SYSDATE, 'HH24') < 8 OR TO_CHAR(SYSDATE, 'HH24') >= 18) THEN raise_application_error( -20502, 'May only change employee table during working hours'); END IF; END; La clusula WHEN.
La clusula WHEN slo es vlida para disparadores con nivel de fila. Si est presente, el cuerpo del disparador slo se ejecutar para las filas que cumplan la condicin especificada en la clusula.
CREATE TRIGGER tr1 BEFORE INSERT OR UPDATE OF salario ON scott.emp FOR EACH ROW WHEN (new.job <> 'PRESIDENT') BEGIN /* Cuerpo del disparador */ END;
Para hacer que un trigger ejecute un ROLLBACK de la transaccin que tiene activa y teniendo en cuenta que en las sentencias que componen el cuerpo de un trigger no puede haber este
Pgina 28 de 32
tipo de sentencias (rollback, commit,) hay que ejecutar error / excepcion mediante la sentencia raise_application_error cuya sintaxis es
RAISE_APPLICATION_ERROR(num_error,mensaje);
El num_error es un nmero entero cualquiera, aunque se aconseja que tenga 5 dgitos. Por ejemplo
raise_application_error( 20000, No se puede modificar el cliente.); Tabla mutando
Cuando se realiza un trigger sobre una tabla, dicha tabla se dice que est en proceso de mutacin, es decir, que se estn realizando cambios sobre ella y que por tanto dentro del trigger no se debe hacer ningn tipo de acceso a dicha tabla con operaciones DML (SELECT, INSERT, DELETE o UPDATE). Si queremos conocer los valores del registro de la tabla sobre la que se ha disparado el trigger, este ltimo debe estar declarado de forma FOR EACH ROW y acceder a sus valores mediante las pseudocolumnas :NEW y :OLD.
Campos Calculados
En algunos SGBDR se permite el uso de campos calculados. Son campos cuyo valor se calcula de forma automtica cuando cambia el valor de alguno de los campos o valores de los que depende. Por ejemplo, en SQL SERVER se hara de la siguiente forma:
CREATE TABLE FacturasL( nNumFac numeric(10) NOT NULL, nNumLin numeric(10) NOT NULL, cCodArt char(13) NOT NULL, nPvpArt numeric(14,4) DEFAULT 0, nUniArt numeric(14,4) DEFAULT 0, nDtoLin numeric(14,4) DEFAULT 0, nBaseImp AS nPvpArt * nUniArt, nAnnio AS YEAR(GetDate()), CONSTRAINT PK_FacturasL PRIMARY KEY (nNumFac, nNumLin) );
En el ejemplo anterior, los campos nBaseImp y nAnnio son calculados y su valor se calcula de forma automtica; as que no pueden ser ni insertados ni modificados. En Oracle no existen los campos calculados, as que, hemos de hacer uso de los triggers para simularlos. La forma de hacerlo es:
1. Creando una segunda tabla, con la misma PRIMARY KEY que la tabla sobre la
Tema 9. PL/SQL de ORACLE. cCodArt VARCHAR(13) NOT NULL, nPvpArt NUMBER(14,4) DEFAULT 0, nUniArt NUMBER(14,4) DEFAULT 0, nDtoLin NUMBER(14,4) DEFAULT 0, CONSTRAINT PK_FacturasL PRIMARY KEY (nNumFac, nNumLin) ); CREATE TABLE FacturasL_Calc( nNumFac NUMBER(10) NOT NULL, nNumLin NUMBER(10) NOT NULL, nBaseImp NUMBER(14,4) DEFAULT 0, nSubTotal NUMBER(14,4) DEFAULT 0, CONSTRAINT PK_FacturasL_Calc PRIMARY KEY (nNumFac, nNumLin) ); CREATE OR REPLACE TRIGGER tr_FacturasL_Ins_Upd AFTER INSERT OR DELETE OR UPDATE ON FacturasL FOR EACH ROW DECLARE PKE Exception; BEGIN
Pgina 29 de 32
-- No se puede cambiar la PK IF Updating('nNumFac') OR Updating('nNumLin') THEN RAISE PKE; -- Lanzar Excepcion END IF; IF Inserting THEN INSERT INTO FacturasL_Calc VALUES (:new.nNumFac,:new.nNumLin, :new.nPvpArt * :new.nUniArt, :new.nPvpArt * :new.nUniArt - (:new.nPvpArt * :new.nUniArt * :new.nDtoLin / 100) ); END IF; IF Deleting THEN DELETE FacturasL_Calc WHERE nNumFac = :old.nNumFac AND nNumLin = :old.nNumLin; END IF; IF Updating('nPvpArt') OR Updating('nUniArt') OR Updating('nDtoLin') THEN UPDATE FacturasL_Calc SET nBaseImp = :new.nPvpArt * :new.nUniArt, nSubTotal = :new.nPvpArt * :new.nUniArt - (:new.nPvpArt * :new.nUniArt * :new.nDtoLin / 100) WHERE nNumFac = :new.nNumFac AND nNumLin = :new.nNumLin; DBMS_OUTPUT.PUT_LINE('Cambio Realizado.'); END IF; EXCEPTION WHEN PKE THEN RAISE_APPLICATION_ERROR (-20001,'Error: No se puede modificar ni Nmero de Factura ni Lnea de Factura.'); WHEN OTHERS THEN RAISE_APPLICATION_ERROR (-20001,'Error: Actualizando campos calculados FacturasL_Calc.'); END ; -----------------------------------------
Pgina 30 de 32
SET SERVEROUTPUT ON; INSERT INTO FacturasL VALUES (1000,1,'HD-SEA01-1TB',60.5,3,10); INSERT INTO FacturasL VALUES (1000,2,'TFT LG 19',195,1,0); COMMIT; SELECT * FROM FacturasL; SELECT * FROM FacturasL_Calc; -- Hasta aqui para probar el INSERT
SET SERVEROUTPUT ON; UPDATE FacturasL SET cCodArt = 'HD-IBM01-3TB' WHERE nNumLin = 1; COMMIT; SELECT * FROM FacturasL; SELECT * FROM FacturasL_Calc; -- Hasta aqui para probar que el cambio de cCodArt no hace nada -- ni siquiera lanza la salida OUTPUT : Cambio Realizado SET SERVEROUTPUT ON; UPDATE FacturasL SET nPvpArt = 99 WHERE nNumLin = 1; COMMIT; SELECT * FROM FacturasL; SELECT * FROM FacturasL_Calc; -- Hasta aqui para probar que el cambio de nPvpArt lanza el Trigger -- Ademas lanza la salida OUTPUT : Cambio Realizado DELETE FacturasL WHERE nNumLin = 1; COMMIT; SELECT * FROM FacturasL; SELECT * FROM FacturasL_Calc; -- Hasta aqui para probar que el borrado de una linea lanza el Trigger
Pgina 31 de 32
Existen dos tipos de datos que no hemos mencionado anteriormente: los registros (o estructuras) y las tablas (o arrays o vectores). Los dos tipos deben ser definidos como un nuevo tipo antes de declarar variables de ese nuevo tipo. El modo de definir nuevos tipos de variables en PL/SQL es a travs de la palabra reservada TYPE:
TYPE nuevo_tipo IS tipo_original.
Una vez definido en nuevo tipo, ya se pueden definir variables de ese nuevo tipo.
Registros:
Los registros no son ms que agrupaciones de tipos de variables que se acceden con el mismo nombre. La sintaxis de definicin de registros es:
TYPE nombre_registro IS RECORD( Campo1 tipo, Campo2 tipo, Campo3 tipo );
Por ejemplo:
TYPE alumno IS RECORD( n_alumno VARCHAR2(5), nombre VARCHAR2(25), tlf VARCHAR2(15) ); Tablas.
Una tabla no es ms que una coleccin de elementos identificados cada uno de ellos por un ndice. En muchos lenguajes se les denomina arrays o matrices. La sintaxis de definicin de tablas es:
TYPE nombre_tabla IS TABLE OF tipo_de_elementos;
Por ejemplo:
DECLARE TYPE array_enteros IS TABLE OF INTEGER; Un_array array_enteros := array_enteros( 0, 0, 0, 0 );
Pgina 32 de 32
Una de las tareas ms importantes de las propias de un desarrollador de bases de datos es la de puesta a punto o tuning. Hay que tener en cuenta que las sentencias SQL pueden llegar a ser muy complejas y conforme el esquema de base de datos va creciendo las sentencias son ms complejas y confusas. Por es difcil escribir la sentencia correcta a la primera. Por todo ello despus de tener cada uno de los procesos escrito, hay que pasar por una etapa de tuning en la que se revisan todas las sentencias SQL para poder optimizarlas conforme a la experiencia adquirida. Tanto por cantidad como por complejidad, la mayora de las optimizaciones deben hacerse sobre sentencias SELECT, ya que son (por regla general) las responsables de la mayor prdida de tiempos. A continuacin se dan unas normas bsicas para escribir sentencias SELECT optimizadas. Las condiciones (tanto de filtro como de join) deben ir siempre en el orden en que est definido el ndice. Si no hubiese ndice por las columnas utilizadas, se puede estudiar la posibilidad de aadirlo, ya que tener ndices de ms slo penaliza los tiempos de insercin, actualizacin y borrado, pero no de consulta. Evitar la condiciones IN ( SELECT) sustituyndolas por joins. Colocar la tabla que devuelve menor nmero de registros en el ltimo lugar del FROM Una consulta cualificada con la clusula DISTINTC debe ser ordenada por el servidor aunque no se incluya la clusula ORDER BY.