Curso de PL/SQL Pág 1 de 6
Transcripción
Curso de PL/SQL Pág 1 de 6
Curso de PL/SQL 7. CONCEPTOS AVANZADOS DE CURSORES EXPLICITOS En este capítulo veremos algunas cosas bastante útiles respecto a los cursores explícitos, como pueden ser los cursores con parámetros (que nos permitirán flexibilizar al máximo nuestros cursores). También usaremos las clausulas FOR UPDATE y WHERE CURRENT OF en los cursores a la hora de actualizar datos y escribiremos cursores que utilicen subconsultas. 7.1 CURSORES CON PARAMETROS Los parámetros nos van a permitir transferir sus valores al cursor cuando este se abre y que la SELECT correspondiente los utilice cuando se ejecute. Esto nos va a permitir abrir el mismo cursor varias veces con distintos parámetros para obtener juegos de resultados diferentes. Interesante ¿no?. Los tipos de datos de los parámetros son los mismos que los de las variables escalares, pero no se especifica el tamaño. Los nombres de los parámetros los usaremos en la expresión de consulta del cursor como ya hemos comentado. Sintaxis: CURSOR nombre_cursor[(param1 [IN] tipo1, .... )] IS consulta ; Como vemos los parámetros son opcionales (hasta ahora no los habíamos usado) y la palabra clave IN también es opcional ya que los parámetros solo pueden ser de “entrada”. Cuando se abre el cursor es cuando se transfieren valores a los parámetros. Veamos un ejemplo: Queremos un cursor que nos permita recorrer los empleados de un departamento determinado. Como cada vez queremos ver un departamento diferente, usaremos éste como parámetro. DECLARE v_departamento DEPARTAMENTOS.codigo%TYPE ; CURSOR c_emple_dep(p_dep IN DEPARTAMENTOS.codigo%TYPE) IS SELECT codigo, nombre, departamento FROM empleados WHERE departamento = p_dep ; BEGIN v_departamento := 10 ; FOR emple IN c_emple(v_departamento) LOOP .... END LOOP ; ... END; Como se puede comprobar, hemos utilizado la declaración de tipos mediante %TYPE para asegurar la consistencia con el tipo de la base de datos del campo del “codigo de departamento”. La variable v_departamento es la que pasamos al cursor como parámetro, por lo que podríamos ir variándola para obtener “juegos” de resultados diferentes en el cursor. Usando un poco lo aprendido con los bucles y utilizando otro cursor para obtener los códigos de departamento, podíamos hacer algo como esto: Pág 1 de 6 Curso de PL/SQL DECLARE CURSOR c_depart IS SELECT codigo FROM departamentos ; CURSOR c_emple_dep(p_dep IN DEPARTAMENTOS.codigo%TYPE) IS SELECT codigo, nombre, departamento FROM empleados WHERE departamento = p_dep ; BEGIN -- Bucle que recorre todos los departamentos FOR depart IN c_depart LOOP -- Bucle que recorre los empleados del -- departamento actual FOR emple IN c_emple(depart.codigo) LOOP .... END LOOP ; END LOOP ; ... END; Como veis, aquí le pasamos al cursor de empleados, la variable depart.codigo que nos viene dada del cursor de departamentos. Podéis reescribir el código utilizando otro tipo de bucle de cursor como ejercicio. Por último, pondremos un ejemplo con más de un parámetro. Ahora queremos recorrer los empleados de un departamento que cobren más de 1000 euros y cuya antigüedad en la empresa sea superior a un año: DECLARE v_departamento DEPARTAMENTOS.codigo%TYPE ; v_salario EMPLEADOS.salario%TYPE ; v_fec_anti DATE ; CURSOR c_emple_dep(p_dep IN DEPARTAMENTOS.codigo%TYPE, p_salario IN EMPLEADOS.salario%TYPE, p_anti IN DATE) IS SELECT codigo, nombre, departamento FROM empleados WHERE departamento = p_dep AND salario > p_salario AND TRUNC(fecha_alta) < p_anti; BEGIN v_departamento := 10 ; v_salario := 1000 ; v_fec_anti := sysdate – 365 ; FOR emple IN c_emple(v_departamento, v_salario, v_fec_anti) LOOP .... .... END LOOP ; ... ... END; Pág 2 de 6 Curso de PL/SQL 7.2 CLAUSULA FOR UPDATE Esta clausula se usa cuando queremos bloquear filas o columnas durante una transacción (actualizar o suprimir filas). La añadiremos en la consulta del cursor para bloquear las filas resultantes cuando se abre el cursor. Como los bloqueos se liberan al final de las transacciones, NO deberemos hacer un COMMIT dentro del cursor si usamos FOR UPDATE. Además hay otro motivo que es causa de error y que lo explicaremos luego de dar algún ejemplo. Sintaxis: SELECT .... FROM .... FOR UPDATE [OF lista_de_columnas] [NOWAIT] lista_de_columnas: comas. es la lista de columnas a bloquear, separadas por NOWAIT: hace que se nos devuelva un error inmediatamente si las filas han sido bloqueadas por otra sesión. Si el servidor Oracle no puede bloquear las filas que se necesitan debido al la clausula FOR UPDATE, espera de forma ininterrumpida, a no ser que hayamos especificado NOWAIT. Podríamos capturar la excepción provocada usando NOWAIT y usar este hecho en un bucle para intentar abrir el cursor n veces antes de que desistamos del intento. Cuando se usa una select con varias tablas, podemos limitar el bloqueo a las filas de algunas de ellas usando FOR UPDATE con la lista de columnas especificadas mediante la notación tabla.campo. La clausula FOR UPDATE es siempre la última que se escribe en una sentencia SELECT. Ejemplo: CURSOR c_emple IS SELECT codigo, nombre, departamento FROM empleados WHERE departamento = 10 FOR UPDATE; En este ejemplo bloqueamos las filas resultantes (empleados del departamento 10) para poder actualizarlas. No especificamos qué o cuales columnas vamos a bloquear. Ejemplo: CURSOR c_emp IS SELECT e.codigo, e.nombre, e.departamento, d.nombre FROM empleados e, departamentos d WHERE d.codigo=e.departamento(+) AND departamento = 10 FOR UPDATE OF e.salario; Aquí si que especificamos qué columna de empleados vamos actualizar ya que en caso contrario bloquearíamos también la fila del departamento 10 en la tabla de DEPARTAMENTOS. Fijaros que tambien he utilizado una “outer join” para unir las dos tablas ya que el departamento de un empleado puede ser NULL y entonces no saldría su registro al no existir tal departamento. Pág 3 de 6 Curso de PL/SQL 7.3 CLAUSULA WHERE CURRENT OF Bien, hemos hablado de bloquear filas para actualizar y/o borrar filas del cursor que estamos recorriendo, pero una vez estoy en la fila que quiero actualizar/borrar ¿cómo lo hago?. Podríamos recoger de la fila actual su clave primaria y ejecutar la sentencia UPDATE ó DELETE de la fila identificada por la clave primaria recogida. Sí es una opción pero, por ejemplo, podemos encontrarnos con una tabla que no tenga clave primaria (mala costumbre, pero os encontrareis muchas así por este mundo). Vale pues para eso tenemos la clausula WHERE CURRENT OF, que va ligada siempre a la clausula FOR UPDATE. Sintaxis: DECLARE CURSOR nombre_cursor IS SELECT ..... FOR UPDATE ; BEGIN FOR nombre_reg IN nombre_cursor LOOP -- actualizamos la fila actual del cursor UPDATE ... WHERE CURRENT OF nombre_cursor ; END LOOP ; COMMIT; END; Observad tres cosas: 1. En la declaración del cursor hemos puesto la clausula FOR UPDATE. 2. En la clausula WHERE CURRENT OF hemos especificado el nombre del cursor NO el de la variable de registro que hace de índice del bucle. 3. El COMMIT está colocado fuera del bucle para evitar que los bloqueos se liberen antes de finalizar todas las sentencias UPDATE que se deban realizar. Finalmente voy a comentar lo que he dicho al comenzar este apartado respecto a poner un COMMIT dentro de un bucle de cursor FOR UPDATE. Y lo voy a hacer de forma tajante: El servidor Oracle puede devolvernos un error (de hecho lo da en tiempo de compilación. Ojo!! en la versión 8i lo da, en la 8.0.5 no, fallando en tiempo de ejecución) pero ES UN ERROR DE PROGRAMADOR. Alguien puede pensar que si los bloqueos no están en juego, por qué no poner el COMMIT dentro para grabar de forma instantánea los cambios y evitar incluso el uso “intensivo” de los segmentos de rollback (que están para almacenar los cambios pendientes de ser aplicados). Pues bien, el cursor puede tener condiciones variadas para obtener el juego de resultados que deseemos (la clausula WHERE de la SELECT del cursor). Si en uno de los cambios “tocamos” el valor de uno de esos campos que forman parte de la condición, podríamos estar “sacando” físicamente del juego de resultados ese registro mientras es evaluado el propio juego de resultados (no digamos nada si lo que hacemos es borrar registros). Incongruencia al canto: Los atributos de cursor se van al traste, el puntero puede descolocarse... Pág 4 de 6 Curso de PL/SQL Como no se si me habéis entendido del todo, lo vemos, como siempre con un ejemplo: DECLARE CURSOR c_emple IS SELECT codigo, nombre, salario FROM empleados WHERE salario <= 1000 FOR UPDATE ; BEGIN FOR emple_reg IN c_emple LOOP ..... ..... -- Si el empleado gana 1000 euros, -- actualizamos la fila actual del cursor -- subiendo el salario un 2% IF emple_reg.salario=1000 THEN UPDATE empleados set salario=salario*1.02 WHERE CURRENT OF c_emple ; END IF; -- grabamos los cambios COMMIT ; ..... END LOOP ; END; En este caso, recuperamos los empleados cuyo salario es inferior o igual a 1000 euros. Dentro del bucle del cursor, llega un momento en que queremos subir el salario de ese empleado un 2% si su sueldo es exactamente 1000 euros, para lo cual ejecutamos la sentencia UPDATE correspondiente y después ejecutamos un COMMIT. ¿Qué hemos hecho?. Pues hemos “sacado” del juego de resultados el registro actual, ya que ahora ese empleado gana más de los mil euros de la condición del cursor. Estamos alterando el contenido del cursor mientras éste está abierto y se produce un error. La solución pasa, simplemente por sacar el COMMIT fuera del bucle del cursor. Sencillamente así: DECLARE CURSOR c_emple IS SELECT codigo, nombre, salario FROM empleados WHERE salario < 1000 FOR UPDATE ; BEGIN FOR emple_reg IN c_emple LOOP ..... ..... -- Si el empleado gana 1000 euros, -- actualizamos la fila actual del cursor -- subiendo el salario un 2% IF emple_reg.salario=1000 THEN UPDATE empleados set salario=salario*1.02 WHERE CURRENT OF c_emple ; END IF; ..... END LOOP ; -- grabamos los cambios COMMIT ; END; Pág 5 de 6 Curso de PL/SQL 7.4 CURSORES CON SUBCONSULTAS Una subconsulta es una consulta (normalmente entre paréntesis) que aparece dentro de otra sentencia DML. Las subconsultas se usan de forma frecuente en la clausula WHERE de una SELECT y también en la claúsula FROM (ver curso de SQL para más detalles sobre las subconsultas). Pues eso, que podemos usar subconsultas dentro de un cursor, así de sencillo. Y con todas las variantes que se quiera (subconsulta, subconsulta sincronizada, etc...) Pág 6 de 6