Cursores
Como se ha explicado anteriormente, las sentencias SQL que devuelven una sola fila se pueden incluir en un bloque PL/SQL. Los datos recuperados por la sentencia SQL se almacenan en las variables mediante la cláusula INTO. ¿Cómo las sentencias SQL son procesadas por el servidor Oracle? El servidor de Oracle asigna un área de memoria privada denominada área de contexto para el procesamiento de sentencias SQL. La sentencia SQL se analiza y se procesa en esta área. La información necesaria para el procesamiento y la información obtenida tras el procesamiento se almacenan en esta área. No setiene control sobre esta área, ya que se gestiona internamente por el servidor Oracle. Un cursor es un puntero a la zona de contexto. Sin embargo, este cursor es un cursor implícito y es gestionado automáticamente por el servidor Oracle. Cuando el bloque PL/SQL ejecuta una sentencia SQL, PL/SQL crea un cursor implícito. Existen dos tipos de cursores:- Cursor Implícito: se crea y está gestionado por el servidor Oracle. No se tiene acceso a él. El servidor de Oracle crea un cursor como cuando tiene que ejecutar una sentencia SQL.
- Cursor Explícito: como programador, es posible que desee recuperar varias filas de una tabla de base de datos, En estos casos, se puede declarar cursores explícitamente en función de las necesidades de negocio. Un cursor declarado por los programadores se denomina cursor explícito. Se debe declara un cursor en la sección declarativa de un bloque PL/SQL
Cursores Explíctos
Los cursores explícitos se emplean para realizar consultas SELECT que pueden devolver cero filas, una o más de una fila.Es necesario declarar un cursor explícito cuando se deben recuperar múltiples filas desde una o varias tablas de la Base de Datos para tener un puntero por cada fila recuperada y trabajar con una fila a la vez. El conjunto de filas retornadas por la query de múltiples filas es llamado Set Activo (Conjunto Activo) y su tamaño es el número de filas que cumplen los criterios de búsqueda de la sentencia SELECT.
Los cursores explícitos pueden ser controlados manualmente en el bloque PL/SQL por el programador. Se deben declarar en la sección Declarativa del bloque PL/SQL.
Control de Cursores Explícitos
Para trabajar con un cursor explícito se requiere:- Declarar el cursor con la instrucción DECLARE asignándole un nombre y definiendo la estructura de la consulta que será asociada al cursor.
- Abrir el cursor con la instrucción OPEN que ejecuta la consulta y reemplaza cualquier variable que es referenciada. Las filas identificadas por la consulta son llamadas al set activo y ahora están disponibles para leerlas.
- Recuperar los datos desde el cursor con la instrucción FETCH leyendo cada fila recuperada por el cursor.
- Cerrar el cursor y liberar los recursos con la instrucción CLOSE: liberando así el set activo de filas. Esto hace posible reabrir el cursor para establecer un nuevo set activo.
1.- La sentencia OPEN ejecuta la consulta asociada con el cursor, identifica el set activo y posiciona el cursor en la primera fila.
2.- La sentencia FETCH recupera la fila actual y avanza el cursor a la siguiente fila hasta que no existan más filas o hasta que se cumpla una condición especificada.
3.- La sentencia CLOSE libera el cursor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| -- Sintaxis CURSOR nombre_cursor IS sentencia_select; -- Ejemplo DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; DECLARE v_locid NUMBER:= 1700; CURSOR cur_dept IS SELECT * FROM departments WHERE location_id = v_locid; |
En la sintaxis:
- nombre_cursor: es un identificador PL/SQL que le da un nombre al cursor.
- sentencia_select: es una instrucción o sentencia SQL que debe ser procesada cuyo resultado quedará en el Cursor.
Una declaración de cursor puede hacer referencia a variables PL/SQL las que deben ser declaradas previamente. El set activo de un cursor es determinado por la sentencia Select en la declaración del cursor. Es obligatorio tener una cláusula INTO para una sentencia Select de un cursor explícito al momento de leer los datos recuperados por el Cursor.
Si se requiere que el procesamiento de las filas sea en un orden específico, se debe usar la cláusula ORDER BY en la query. El cursor puede ser cualquier sentencia SQL válida, incluyendo joins, subqueries y otras.
En el ejemplo de la izquierda, se declara un cursor explícito cur_emp el que obtendrá la identificación y apellidos de los empleado que trabajan en el departamento 30. El ejemplo de la derecha, el valor de la localidad está asignado a la variable v_locid la que es utilizada en la condición de la sentencia del cursor cur_dept. Por lo tanto el cursor obtendrá la información del departamento cuya localidad sea 1700.
1
2
3
4
5
6
7
8
9
10
11
12
13
| -- Sintaxis OPEN nombre_cursor; -- Ejemplo DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; ... BEGIN OPEN emp_cursor; |
Abrir el Cursor
En la sintaxis:
nombre_cursor: es el cursor declarado en la sección de Declaración del bloque PL/SQL.
La sentencia OPEN está incluida en la sección Ejecutable de un bloque PL/SQL y es una sentencia ejecutable que realiza las siguientes operaciones:
- Dinámicamente se asigna memoria para un área de contexto.
- Parsea la sentencia SELECT.
- Se examinan los valores de las variables de entrada.
- Identifica el conjunto o set activo que satisface el criterio de búsqueda, basándose en los valores de variables y el contenido de las tablas que forman parte de la consulta.
- Posiciona el puntero en la primera fila del conjunto activo. Una vez abierto el cursor no se puede volver a abrir hasta que no se cierre. En el ejemplo, se abre el cursor cur_emp declarado inicialmente.
- %ISOPEN: Atributo de tipo Booleano que retorna TRUE si el cursor se encuentra abierto.
- %NOTFOUND: Atributo de tipo Booleano que retorna TRUE si el FECTH más reciente no retorna filas.
- %FOUND: Atributo de tipo Booleano que Retorna TRUE si el FETCH más reciente retorna una fila.
- %ROWCOUNT: Atributo de tipo numérico que retorna el número total de filas procesadas.
- nombre_cursor: es un identificador PL/SQL para el cursor declarado.
- nombre_parámetro: es el nombre de un parámetro.
- tipo_dato: es el tipo de dato escalar del parámetro.
- sentencia_select: es una instrucción o sentencia SQL que debe ser procesada cuyo resultado quedará en el Cursor.
- valor_parámetro: es el valor que se le asigna al parámetro definido en el cursor.
- nombre_cursor: es un identificador PL/SQL para el cursor declarado.
- columna_referenciada: es una columna en la tabla sobre la cual se realiza la query.
- NOWAIT: retorna un error del Servidor Oracle si las filas están bloqueadas por otra sesión.
- WAIT n: especifica el número de segundos para esperar y chequear si las filas están bloqueadas. Si la filas están bloqueadas después de n segundos retorna un error.
- nombre_cursor : es el nombre de un cursor declarado. El cursor debe haber sido declarado con la cláusula FOR UPDATE.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| -- Sintaxis FETCH nombre_cursor INTO lista_de_variables; FETCH nombre_cursor INTO registro_PL/SQL; -- Ejemplo DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; v_lname employees.last_name%TYPE; v_empno employees.employee_id%TYPE; BEGIN OPEN cur_emp; FETCH cur_emp INTO v_empno, v_lname; DBMS_OUTPUT.PUT_LINE(v_empno || ' ' || v_lname); END ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| DECLARE CURSOR c_emp_cursor IS SELECT employee_id, last_name FROM employees WHERE department_id =30; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; BEGIN OPEN c_emp_cursor; LOOP FETCH c_emp_cursor INTO v_empno, v_lname; EXIT WHEN c_emp_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE( v_empno || ' ' ||v_lname); END LOOP; END ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| -- Sintaxis CLOSE nombre_cursor; -- Ejemplo DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; lname employees.last_name%TYPE; empno employees.employee_id%TYPE; BEGIN OPEN cur_emp; FETCH emp_cursor INTO empno, lname; DBMS_OUTPUT.PUT_LINE(empno || ' ' || lname); CLOSE cur_emp; END ; |
Atributos para Cursores Explícitos
Hay cuatro atributos para obtener información del estado de un cursor explícito. Cuando se agrega el nombre del cursor, estos atributos devuelven información útil acerca de la ejecución de una sentencia de manipulación del cursor.
1
2
3
4
5
| IF NOT cur_emp%ISOPEN THEN OPEN cur_emp; END IF; LOOP FETCH cur_emp ... |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| DECLARE v_empleado VARCHAR2(60); CURSOR cur_emp IS SELECT first_name || ' ' || last_name FROM employees; BEGIN OPEN cur_emp; LOOP FETCH cur_emp INTO v_empleado; EXIT WHEN cur_emp%ROWCOUNT > 10 OR cur_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_empleado); END LOOP; CLOSE cur_emp; END ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id = 30; reg_emp cur_emp%ROWTYPE; BEGIN OPEN cur_emp; LOOP FETCH cur_emp INTO reg_emp; EXIT WHEN cur_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE(reg_emp.employee_id || ' ' || reg_emp.last_name); END LOOP; CLOSE cur_emp; END ; |
Manejo del Cursor: Loop Simple
Para poder iterar a través del cursor se puede usar LOOP Simple. Esto permite leer todas las filas (o una cantidad de filas determinadas) del cursor de acuerdo a la condición de salida del loop.
1
2
3
4
5
6
7
| OPEN nombre_cursor; LOOP FETCH nombre_cursor INTO lista_variables | registro_PL/SQL; EXIT WHEN nombre_cursor%NOTFOUND | EXIT WHEN nombre_cursor%ROWCOUNT; /* Procesamiento de los registros recuperados y ejecución de sentencias */ END LOOP; CLOSE nombre_cursor; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; lname employees.last_name%TYPE; empno employees.employee_id%TYPE; BEGIN OPEN cur_emp; LOOP FETCH emp_cursor INTO empno, lname; EXIT WHEN cur_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE(empno || ' ' || lname); END LOOP; CLOSE cur_emp; END ; |
Manejo del Cursor: WHILE LOOP
La instrucción FETCH aparece dos veces. Para leer todas filas se utiliza WHILE LOOP. Para validar que existan filas en el cursor se utiliza el atributo %FOUND.
1
2
3
4
5
6
7
8
| OPEN nombre_cursor; FETCH nombre_cursor INTO lista_variables; WHILE nombre_cursor%FOUND LOOP /* Procesamiento de los registros recuperados y ejecución de sentencias*/ FETCH nombre_cursor INTO lista_variables; END LOOP; CLOSE nombre_cursor; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; empno employees.employee_id%TYPE; lname employees.last_name%TYPE; BEGIN OPEN cur_emp; FETCH cur_emp INTO empno, lname; WHILE cur_emp%FOUND LOOP DBMS_OUTPUT.PUT_LINE( empno || ' ' ||lname); FETCH emp_cursor INTO empno, lname; END LOOP; CLOSE cur_emp; END ; |
Manejo del Cursor: FOR LOOP
El ciclo FOR LOOP es el método más fácil para procesar cursores explícitos ya que se ejecuta implícitamente las instrucciones OPEN, FETCH, EXIT y CLOSE. El loop finaliza automáticamente cuando la última fila es leída. El registro es declarado implícitamente. Los campos del registro serán las columnas seleccionadas en el cursor. En la sintaxis: nombre_registro : es el nombre del registro declarado implícitamente. nombre_cursor : es un identificador PL/SQL para el cursor declarado previamente.
1
2
3
| FOR nombre_registro IN nombre_cursor LOOP /* Procesamiento de los registros recuperados y ejecución de sentencias*/ END LOOP; |
1
2
3
4
5
6
7
8
9
10
| DECLARE CURSOR cur_emp IS SELECT employee_id, last_name FROM employees WHERE department_id =30; BEGIN FOR reg_emp IN cur_emp LOOP DBMS_OUTPUT.PUT_LINE( reg_emp.employee_id || ' ' || reg_emp.last_name); END LOOP; END ; |
1
2
3
4
5
6
7
| BEGIN FOR emp_record IN ( SELECT employee_id, last_name FROM employees WHERE department_id =30) LOOP DBMS_OUTPUT.PUT_LINE( emp_record.employee_id || ' ' ||emp_record.last_name); END LOOP; END ; |
Cursores con Parámetros
Se pueden pasar parámetros a un cursor. Esto significa que se puede abrir y cerrar un cursor explícito varias veces en un bloque, devolviendo un set activo diferente en cada ocasión. Para cada ejecución, el cursor se debe haber cerrado con anterioridad para poder ser abierto nuevamente con un nuevo conjunto de parámetros. Cada parámetro formal en la declaración del cursor debe tener su valor correspondiente en la sentencia OPEN. Los valores de los parámetros pasan al cursor cuando se abre y la consulta es ejecutada.Es particularmente útil cuando el mismo cursor es referenciado en forma repetida pero con valores diferentes.
En la sintaxis:
1
2
3
4
| CURSOR nombre_cursor[(nombre_parámetro tipo_dato, ...)] IS sentencia_select; OPEN nombre_cursor(valor_parámetro,.....) ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| DECLARE CURSOR emp_cursor (p_deptno NUMBER) IS SELECT employee_id, last_name FROM employees WHERE department_id = p_deptno; v_empno employees.employee_id%TYPE; v_lname employees.last_name%TYPE; BEGIN OPEN emp_cursor (30); DBMS_OUTPUT.PUT_LINE( 'Empleados Depto 30' ); LOOP FETCH emp_cursor INTO v_empno, v_lname; EXIT WHEN emp_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE( ' ' || rpad(v_empno, 19, ' ' ) || ' ' ||v_lname); END LOOP; CLOSE emp_cursor; OPEN emp_cursor (20); DBMS_OUTPUT.NEW_LINE(); DBMS_OUTPUT.PUT_LINE( 'Empleados Depto 20' ); LOOP FETCH emp_cursor INTO v_empno, v_lname; EXIT WHEN emp_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE( ' ' || rpad(v_empno, 19, ' ' ) || ' ' || v_lname); END LOOP; CLOSE emp_cursor; END ; |
Cláusula FOR UPDATE
Si hay varias sesiones enuna sola base de datos, existe la posibilidad de que las filas de una tabla en particular sean actualizadas después de abrir el cursor. Estos datos actualizados se verán sólo cuando se vuelve a abrir el cursor. Por lo tanto, es mejor tener bloqueadas las filas antes de actualizar o eliminar filas. Puede bloquear las filas con la cláusula FOR UPDATE en la consulta del cursor.La cláusula FOR UPDATE es la última cláusula en una sentencia SELECT, incluso después de ORDER BY (si existe). Al consultar varias tablas, se puede utilizar la cláusula FOR UPDATE para limitar el bloqueo de filas a las tablas particulares. FOR UPDATE col_name (s) bloquea filas sólo en tablas que contienen col_name (s).
En la sintaxis:
1
2
3
4
5
6
7
8
9
10
11
12
| -- Sintaxis CURSOR nombre_cursor SELECT ... FROM ... FOR UPDATE [ OF column_referenciada][NOWAIT | WAIT n]; -- Ejemplo DECLARE CURSOR c_emp_cursor IS SELECT employee_id, last_name FROM employees WHERE department_id = 80 FOR UPDATE OF salary NOWAIT; |
Cláusula WHERE CURRENT
Es usada en conjunto con la cláusula FOR UPDATE cuando se utiliza un cursor SELECT FOR UPDATE para referenciar la fila actual en el cursor explícito. Se usa la combinación de ambas cláusulas para actualizar o eliminar la fila actual en la correspondiente tabla de la Base de datos.En la sintaxis:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| DECLARE empleado employees%ROWTYPE; CURSOR emp_cursor IS SELECT * FROM employees WHERE department_id = 100 FOR UPDATE ; BEGIN OPEN emp_cursor; -- Se produce el bloqueo LOOP FETCH emp_cursor INTO empleado; EXIT WHEN emp_cursor%NOTFOUND; IF empleado.salary < 7000 THEN UPDATE employees SET salary=salary + salary *.10 WHERE CURRENT OF emp_cursor; END IF; END LOOP; CLOSE emp_cursor; -- No libera bloqueos COMMIT ; /*Libera el bloqueo de las filas del cursor FOR UPDATE */ END ; |
Cursores con Subconsultas
Una subconsulta es una consulta SQL, generalmente entre paréntesis, que está dentro de otra sentencia SQL. Cuando es evaluada, la subconsulta proporciona una valor o set de valores para la consulta externa. Las subconsultas son usadas a menudo en la cláusula WHERE de una sentencia Select. Pueden ser usadas también en una cláusula FROM creando una fuente de dato temporal para la consulta. El bloque del ejemplo, muestra a los empleados que posean un salario menor al salario promedio. (El resultado se muestra en la siguiente página).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| DECLARE CURSOR mi_cursor IS SELECT first_name || ' ' || last_name, salary FROM employees WHERE salary < ( SELECT ROUND( AVG (salary)) FROM employees) ORDER BY salary, last_name; nombre VARCHAR2(50); salario employees.salary%TYPE; BEGIN OPEN mi_cursor; DBMS_OUTPUT.PUT_LINE( ' FUNCIONARIOS CON SALARIO MENOR AL PROMEDIO ' ); DBMS_OUTPUT.PUT_LINE( ' ---------------------------------------- ' ); LOOP FETCH mi_cursor INTO nombre, salario; EXIT WHEN mi_cursor%NOTFOUND; DBMS_OUTPUT.PUT_LINE(rpad(nombre, 25, ' ' ) || ' : ' || TO_CHAR(salario, '$999,999' )); END LOOP; CLOSE mi_cursor; END ; |
Trabajando con más de un cursor
Se trabaja con más de un cursor a la vez cuando alguno de los valores recuperado del cursor principal es utilizado como parámetro en otro cursor. Primero se abre el cursor principal cuyos valores servirán como parámetros al siguiente cursor. Obtenidos los valores del primer cursor, se puede abrir el siguiente cursor. Los cursores se deben manejar con loops anidados ya sea utilizando el LOOP Simple o FOR LOOP.En el ejemplo, por cada departamento leído (primer cursor) se leen desde el segundo cursor todos los empleados que pertenecen al departamento. Una vez leídos todos los empleados se lee el siguiente departamento para obtener los empleados del nuevo departamento leído y así sucesivamente hasta cuando ya no existan más departamentos que leer desde el cursor principal.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| DECLARE CURSOR cur_deptos IS SELECT department_id, department_name FROM departments; CURSOR cur_emp_depto(deptno NUMBER) IS SELECT first_name || ' ' || last_name nombre_emp FROM employees WHERE department_id = deptno; v_total_emp NUMBER(2); BEGIN DBMS_OUTPUT.PUT_LINE( ' Empleados por Departamento' ); DBMS_OUTPUT.PUT_LINE( '===================================' ); DBMS_OUTPUT.NEW_LINE(); FOR reg_deptos IN cur_deptos LOOP DBMS_OUTPUT.PUT_LINE( 'Departamento: ' || reg_deptos.department_name); DBMS_OUTPUT.PUT_LINE( '=====================================' ); v_total_emp := 0; FOR reg_emp_depto IN cur_emp_depto(reg_deptos.department_id) LOOP DBMS_OUTPUT.PUT_LINE(reg_emp_depto.nombre_emp); v_total_emp := v_total_emp + 1; END LOOP; DBMS_OUTPUT.PUT_LINE( '=====================================' ); DBMS_OUTPUT.PUT_LINE( 'Total de Empleados: ' || v_total_emp); DBMS_OUTPUT.NEW_LINE(); END LOOP; END ; |
No hay comentarios:
Publicar un comentario