You are on page 1of 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: es la lista de columnas a bloquear, separadas por


comas.

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

You might also like