lunes, 25 de mayo de 2015

Manejando Excepciones

Manejo de Excepciones

DECLARE
  lname VARCHAR2(15);
BEGIN
  SELECT last_name INTO lname 
      FROM employees 
   WHERE first_name='John'; 
  DBMS_OUTPUT.PUT_LINE ('El apellido de John es  : ' ||lname);
END;

En el bloque PL/SQL del ejemplor, la sentencia SELECT retorna más de una fila por lo tanto se produce un error Oracle que no está controlado en el bloque PL/SQL terminando la ejecución de éste en forma errónea.

Una excepción es un error PL/SQL que se genera durante la ejecución del programa.
Una excepción se puede generar:

  • Implícitamente por el Servidor Oracle:
    • Un error Oracle ocurre y la excepción asociada se ejecuta automáticamente.
    • Por ejemplo un error ORA-01403 ocurre cuando no se recuperan filas desde la Base de Datos a través de una sentencia SELECT gatillándose la excepción NO_DATA_FOUND.
    • Estos errores son convertidos en excepciones predefinidas.
  • Explícitamente por el programa PL/SQL:
    • Las excepciones se deben generar explícitamente.
    • Una excepción se genera explícitamente usando la sentencia RAISE dentro del bloque la cual puede generar excepciones definidas por el usuario o excepciones predefinidas.
    DECLARE
      lname VARCHAR2(15);
    BEGIN
      SELECT last_name INTO lname 
          FROM employees 
       WHERE first_name='John'; 
       DBMS_OUTPUT.PUT_LINE ('John''s last name is : ' ||lname);
    EXCEPTION
      WHEN TOO_MANY_ROWS THEN
      DBMS_OUTPUT.PUT_LINE ('La sentencia SELECT recupera múltiples filas. Considere usar un Cursor Explícito.');
    END;
    

    Ahora en el bloque PL/SQL del ejemplo se ha definido la Sección de Excepciones para poder controlar cuando una sentencia SQL retorne más de una fila.

    Controlando una Excepción:
    Se debe incluir una sección EXCEPTION en el bloque PL/SQL para que controle las excepciones.
    Si la excepción se genera en la sección ejecutable del bloque, el control pasa al manejador de esa excepción definido en la Sección de Excepciones del bloque PL/SQL.
    Si el bloque PL/SQL maneja satisfactoriamente la excepción, entonces la excepción no se propaga al bloque superior (si es que existe) o devuelve el control al Sistema.
    El bloque PL/SQL termina satisfactoriamente.

    Propagando una Excepción:
    Si la excepción se genera en la sección Ejecutable del bloque y ésta no tiene un correspondiente manejador de excepción, el bloque PL/SQL termina con fallas y la excepción se propaga al bloque superior o devuelve el control a la aplicación que ejecutó el programa PL/SQL.


    Tipos de Excepciones

    Predefinidas del Servidor Oracle:
    Son generadas implícitamente.
    No necesitan ser definidas pues son predefinidas por el Servidor Oracle.
    Son aproximadamente 20 excepciones que controlan errores particulares que pueden ocurrir en un bloque PL/SQL (excepto OTHERS que controla cualquier tipo de error).

    No Predefinidas del Servidor Oracle:
    Son generadas implícitamente.
    Corresponden a cualquier otro error estándar de Oracle pero que no posee una excepción predefinida asociada.
    Son declaradas dentro de la sección Declarativa y permiten que el Servidor Oracle las genere implícitamente.

    Definidas por el usuario:
    Es una condición que el desarrollador determina como anormal.
    Se Declaran en la sección Declarativa y generadas explícitamente.


    Manejando Excepciones

    Se puede manejar cualquier error al incluir un controlador correspondiente dentro de la sección de control de excepciones del bloque PL/SQL. Cada controlador consiste en una cláusula WHEN, que especifica un nombre de excepción, seguido por una secuencia de sentencias que se ejecutan cuando se produce esta excepción. Se pueden incluir las excepciones que se deseen dentro de una sección de excepciones para controlar excepciones específicas. Sin embargo, no se puede tener varios controladores para una misma excepción.

    EXCEPTION
      WHEN excepción1 [OR excepción2 . . .] THEN
        sentencia1;
        sentencia2;
        . . .
      [WHEN excepción3 [OR excepción4 . . .] THEN
        sentencia1;
        sentencia2;
        . . .]
      [WHEN OTHERS THEN
        sentencia1;
        sentencia2;
        . . .]
    

    En la sintaxis:
    excepción : es el nombre estándar de una excepción predefinida o el nombre de una excepción definida por el usuario declarada en la sección Declarativa.
    sentencia : es una o más sentencias PL/SQL o SQL que se ejecutarán si ocurre la excepción controlada.
    OTHERS: es una cláusula opcional de manejo de excepciones que controla cualquier excepción que no se está manejando explícitamente.


    Controlando Excepciones

    La palabra clave EXCEPTION comienza la sección de manejo de excepciones.
    Se debe especificar un nombre a la excepción seguido de una secuencia de sentencias que se ejecutarán cuando la excepción se genere.
    Se puede incluir un número indefinido de gestores dentro de la sección EXCEPTION para manejar excepciones específicas.
    Sólo un gestor de excepciones es procesado antes de que la ejecución del bloque finalice.
    WHEN OTHERS debe ser la última cláusula en la sección de manejo de excepciones. La excepción OTHERS controla cualquier error que se produce en un bloque PL/SQL y que no está controlado en la sección de Excepciones del bloque. Debe ir asociada a las funciones SQLCODE y SQLERRM.
    Se puede puede tener sólo una cláusula OTHERS.
    Si se desea manejar la misma excepción para diferentes sentencias SQL, entonces se debe generar un bloque por separado para cada sentencia y cada bloque debe tener su propia sección de Excepción.


    Controlando Excepciones Predefinidas del Servidor Oracle

    Se debe referenciar el nombre predefinido del excepción en la sección que maneja la excepción.
    Las excepciones predefinidas son:
    ACCESS_INTO_NULL (ORA-06530) : Se ha intentado asignar valores a los atributos de un objeto que no se ha inicializado.
    CASE_NOT_FOUND (ORA-06592) : Ninguna de las opciones en la cláusula WHEN de una sentencia CASE se ha seleccionado y no se ha definido la cláusula ELSE.
    COLLECTION_IS_NULL (ORA-06531) : Se intentó asignar valores a una tabla anidad o varray aún no inicializada.
    CURSOR_ALREADY_OPEN (ORA-06511) : Se intentó abrir un cursor que ya se encuentra abierto.
    DUP_VAL_ON_INDEX (ORA-00001) :Se intentó ingresar un valor duplicado en una columna(s) definida(s) como Clave Primaria o Unique en la tabla.
    INVALID_CURSOR (ORA-01001) : Se ha intentado efectuar una operación no válida sobre un cursor.
    INVALID_NUMBER (ORA-01722) : La conversión de una cadena de caracteres a número ha fallado cuando esa cadena no representa un número válido.
    LOGIN_DENIED (ORA-01017) : Se ha conectado al servidor Oracle con un nombre de usuario o password inválido.
    NO_DATA_FOUND (ORA-01403) : Una sentencia SELECT no retornó valores o se ha referenciado a un elemento no inicializado en una tabla indexada.
    NOT_LOGGED_ON (ORA-01012) : El programa PL/SQL efectuó una llamada a la Base de Datos sin estar conectado al servidor Oracle
    PROGRAM_ERROR (ORA-06501) : PL/SQL tiene un problema interno.
    STORAGE_ERROR (ORA-06500) : PL/SQL se quedó sin memoria o la memoria está corrupta.
    SUBSCRIPT_BEYOND_COUNT (ORA-06533) : Se ha referenciado un elemento de una tabla anidad o índice de varray mayor que el número de elementos de la colección.
    SUBSCRIPT_OUTSIDE_LIMIT (ORA-06532) : Se ha referenciado un elemento de una tabla anidada o índice de varray fuera del rango (Ej. -1).
    SYS_INVALID_ROWID (ORA-01410) : Fallo al convertir una cadena de caracteres a un tipo ROWID.
    TIMEOUT_ON_RESOURCE (ORA-00051) : Se excedió el tiempo máximo de espera por un recurso de Oracle.
    TOO_MANY_ROWS (ORA-01422) : Una sentencia SELECT INTO retorna más de una fila.
    VALUE_ERROR (ORA-06502) : Ocurrió un error aritmético, de conversión o truncamiento. Por ejemplo cuando se intenta almacenar un valor muy grande en una variable más pequeña.
    ZERO_DIVIDE (ORA-01476) : El programa intentó hacer una división por cero.

    DECLARE
     v_lname VARCHAR2(15);
    BEGIN
      SELECT last_name 
           INTO v_lname 
          FROM employees 
       WHERE first_name = 'Juanito'; 
       DBMS_OUTPUT.PUT_LINE ('John''s last name is : ' || v_lname);
    EXCEPTION
      WHEN TOO_MANY_ROWS THEN
          DBMS_OUTPUT.PUT_LINE ('La sentencia SELECT recupera múltiples filas');
      WHEN NO_DATA_FOUND THEN
          DBMS_OUTPUT.PUT_LINE ('La sentencia SELECT no recupera fila');
    END;
    

    En el ejemplo, el bloque obtiene el apellido del empleado con nombre Juanito. Como no existe un empleado con ese nombre se genera el error ORA-01403 asociado a la excepción NO_DATA_FOUND. Como esa excepción está controlada en el bloque PL/SQL se ejecutan las sentencias definidas después del THEN de esa excepción y el bloque finaliza en forma correcta.

    CREATE SEQUENCE seq_errores;
    CREATE TABLE errores
    (correlativo           NUMBER(10) CONSTRAINT PK_ERRORES PRIMARY KEY,
     nombre_proceso  VARCHAR2(80),
     mensaje_error      VARCHAR2(255));
    
    BEGIN
      INSERT INTO departments(department_id, department_name, manager_id, location_id)
          VALUES(10, 'Depto Nuevo', 200, 1700);
    EXCEPTION
      WHEN DUP_VAL_ON_INDEX  THEN
         INSERT INTO errores 
         VALUES(seq_errores.NEXTVAL, 'Bloque PL/SQL Inserta Departamento',
                        'Insertando un valor de Clave Primaria que ya existe');
         COMMIT;
    END;
    

    En el bloque del ejemplo se está insertando un nuevo departamento con identificación 10. La clave de esa tabla es la identificación del departamento (department_id). Como el departamento 10 ya existe se genera un error Oracle ORA-00001 asociado a la excepción DUP_VAL_ON_INDEX. Como esa excepción está controlada en el bloque PL/SQL se ejecutan las sentencias definidas después del THEN de esa excepción que son insertar en la tabla errores información del error (que es la que se visualiza) y el bloque finaliza en forma correcta.

    DECLARE
      lname VARCHAR2(15);
    BEGIN
      SELECT last_name INTO lname 
          FROM employees 
       WHERE first_name='John'; 
       DBMS_OUTPUT.PUT_LINE ('John''s last name is : ' ||lname);
    EXCEPTION
     WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Se ha producido un error en el Bloque PL/SQL' || SQLCODE);
    END;
    

    En el ejemplo, el bloque obtiene el apellido del empleado con nombre Juanito. Como no existe un empleado con ese nombre se genera el error ORA-01403 asociado a la excepción NO_DATA_FOUND. Como esa excepción está controlada en el bloque PL/SQL se ejecutan las sentencias definidas después del THEN de esa excepción y el bloque finaliza en forma correcta.


    Funciones SQLCODE y SQLERRM

    Cuando ocurre una excepción, se puede identificar el código de error asociado o el mensaje de error usando funciones predefinidas.
    Las funciones que permiten aclarar al usuario la situación de error son:
    SQLCODE: retorna el número de error Oracle para excepciones internas.
    SQLERRM: retorna el mensaje de error asociado con el número de error.
    Las funciones SQLCODE y SQLERRM no se pueden utilizar directamente en una sentencia SQL, por ejemplo en una sentencia INSERT. Para ello se deben asignar los valores a funciones locales y usar esas variables en la sentencia SQL. Sus valores se pueden visualizar directamente.

    DECLARE
      v_cod_error      NUMBER(10);
      v_men_error     VARCHAR2(255);
    BEGIN 
       INSERT INTO departments (department_id, department_name) 
       VALUES (280, NULL);
    EXCEPTION
    WHEN OTHERS THEN
      v_cod_error := SQLCODE;
      v_men_error := SQLERRM;
       INSERT INTO errores 
         VALUES(seq_errores.NEXTVAL, 'Bloque PL/SQL Inserta Depto Nulo. Código Error: ' || v_cod_error,
                        v_men_error);
         COMMIT;
    END;
    

    En el bloque PL/SQL del ejemplo, la excepción OTHERS controla cualquier error que se produzca durante la ejecución del bloque. En este caso al querer insertar un nuevo departamento, se inserta NULL en la columna department_name que en la tabla está definido como NOT NULL. Por esta razón de genera el error Oracle -01400. Como se va a insertar en la tabla errores, se debe almacenar antes en las variables locales definidas el resultado de las funciones SQLCODE y SQLERRM. Posterior es esto se inserta en la tabla errores (que es lo que se visualiza en el ejemplo).


    Bloques Anidados para controlar Excepciones

    DECLARE
    nombre   employees.first_name%type;
    registro countries%ROWTYPE;
    BEGIN
        SELECT first_name INTO nombre
           FROM employees
         WHERE employee_id = 100;
         SELECT * INTO registro
            FROM countries
         WHERE country_id='ZZ';
    EXCEPTION
    WHEN NO_DATA_FOUND THEN
       DBMS_OUTPUT.PUT_LINE('No hay filas seleccionadas');
    END;
    

    En el bloque posee una sección de Control de Excepciones para controlar el error cuando la sentencia SELECT no retorne filas. En este caso, es la segunda sentencia en donde no se encuentran filas, pero con el mensaje que muestra el bloque no se sabe que sentencia es la que falló.

    DECLARE
    nombre   employees.first_name%type;
    registro countries%ROWTYPE;
    BEGIN
        SELECT first_name INTO nombre
           FROM employees
         WHERE employee_id = 100;
                BEGIN
                        SELECT * INTO registro
                            FROM countries
                         WHERE country_id='ZZ';
                EXCEPTION
                WHEN NO_DATA_FOUND THEN
                DBMS_OUTPUT.PUT_LINE('No hay fila en countries');
                END;
    EXCEPTION
    WHEN NO_DATA_FOUND THEN
       DBMS_OUTPUT.PUT_LINE('No hay fila en employees');
    END;
    

    Basados en el ejemplo anterior, se genera un bloque anidado para la segunda sentencia SELECT. Por lo tanto ahora cada bloque posee su propia sección de Control de Excepciones permitiendo controlar en forma independiente cuando no se encuentren filas en las tablas employees o countries.

    DECLARE
      CURSOR c_empleados IS
        SELECT employee_id, department_id, salary
          FROM employees;
    v_nom_depto  VARCHAR2(35);
    BEGIN
     FOR reg_empleados IN c_empleados LOOP
         BEGIN
              SELECT department_name 
                   INTO v_nom_depto
                 FROM departments
              WHERE department_id = reg_empleados.department_id;
              DBMS_OUTPUT.PUT_LINE('El empleado ' || reg_empleados.employee_id || ' trabaja en el departamento'  || v_nom_depto);
              EXCEPTION
              WHEN NO_DATA_FOUND THEN
              DBMS_OUTPUT.PUT_LINE('El departamento del empleado ' || reg_empleados.employee_id || ' No existe');
              END;
     END LOOP;
    END;
    

    En el ejemplo, el bloque interno creado para la sentencia que obtiene el nombre del departamento de cada empleado, controla si el departamento leído no existe en la tabla departments (que es el caso del empleado 178) muestra el mensaje de la excepción. Esto permite, que a pesar de que el departamento leído desde el cursor no exista en la tabla departments el proceso continué en forma normal para procesado todos los empleados almacenados en el cursor. (El resultado del bloque se muestra en la siguiente página). Si no se genera un bloque interno para que controle la excepción, la ejecución del bloque finalizaría cuando se procese el empleado 178.


    Controlando Excepciones No Predefinidas del Servidor Oracle

    Estas excepciones son similares a las excepciones predefinidas sin embargo no se encuentran definidas como excepciones en el servidor Oracle. Son errores estándares de Oracle pero que no poseen una excepción definida en Oracle (nombre de excepción).
    Se pueden crear excepciones con errores estándares de Oracle utilizando la función PRAGMA EXCEPTION_INIT. PRAGMA es la palabra clave que significa que la declaración es una directiva del compilador. En PL/SQL, EXCEPTION_INIT es una PRAGMA que le dice al compilador PL/SQL que se asocia un nombre de excepción con un error Oracle. Esto permite interpretar todas las presencias del nombre de excepción dentro del bloque como el número de error del servidor Oracle asociado. (SQLCODE)

    DECLARE
     nombre_excepción EXCEPTION;	
     PRAGMA EXCEPTION_INIT (nombre_excepción, número_error); 	            
    BEGIN
    ………………………………………………………………....
    ………………………………………………………………....
    
    EXCEPTION
     WHEN nombre_excepción THEN   	
    ………………………………………………………………....		
    END;
    

    En la sintaxis:
    nombre_excepción EXCEPTION:
    nombre_excepción es el nombre de la excepción a crear
    PRAGMA EXCEPTION_INIT(nombre_excepción, número_error);
    nombre_excepción es la excepción previamente declarada
    número_error es un número de error standard de Oracle
    WHEN excepción THEN
    nombre_excepción es la referencia a la excepción declarada en la sección Declarativa y que fué asociada a la función PRAGMA_EXCEPTION_INIT

    DECLARE
     excepcion_insert  EXCEPTION;                                    
     PRAGMA EXCEPTION_INIT(excepcion_insert, -01400);                                            
    BEGIN 
       INSERT INTO departments (department_id, department_name) 
       VALUES (280, NULL);
    EXCEPTION
     WHEN excepcion_insert THEN
     DBMS_OUTPUT.PUT_LINE('OPERACIÓN DE INSERT HA FALLADO');
     DBMS_OUTPUT.PUT_LINE(SQLERRM); 
    END;
    

    En el ejemplo, el bloque PL/SQL controla el error del Servidor Oracle -01400, no puede insertar NULL:
    1 : se declara excepción excepcion_insert.
    2 : con la función PRAGMA EXCEPTION_INIT se asocia la excepción declarada con el número de error standard de Oracle.
    3 : se referencia la excepción declarada. La función SQLERRM es usada para recuperar el mensaje de error Oracle.


    Controlando Excepciones Definidas por el Usuario

    PL/SQL permite definir excepciones propias del usuario según se requiera. Las excepciones PL/SQL definidas por el usuario se deben:
    Declarar en la sección de Declaración del bloque PL/SQL.
    Generar explícitamente con la sentencia RAISE en la sección Ejecutable.
    Manejar en la sección de manejo de Excepciones (EXCEPTION).

    SET SERVEROUTPUT ON
    DECLARE
     nombre_excepción  EXCEPTION; 
    ………………………………….…………….
    BEGIN
    ………………………………….…………….
       IF condición THEN 
           RAISE nombre_excepción ; 
    ………………………………….…………….
    ………………………………….…………….
    EXCEPTION
     WHEN nombre_excepción THEN 
    ………………………………….…………….
    END;
    

    En la sintaxis:
    nombre_excepción EXCEPTION:
    nombre_excepción es el nombre de la excepción definida por el usuario.
    RAISE nombre_excepción se utiliza para generar explícitamente la excepción definida por el usuario;
    WHEN excepción THEN
    nombre_excepción es la referencia a la excepción definida por el usuario en la sección Declarativa.

    DECLARE
      depart_invalido EXCEPTION;
    BEGIN
      UPDATE departments
              SET department_name = 'Depto nuevo'
       WHERE  department_id = 999;
        IF SQL%NOTFOUND THEN
             RAISE depart_invalido;
      END IF;
      COMMIT;
    EXCEPTION
      WHEN depart_invalido  THEN
        DBMS_OUTPUT.PUT_LINE('No existe departamento.');
    END;
    

    En el bloque del ejemplo, se define una excepción para controlar cuando no existan filas que actualizar. A diferencia de la sentencia SELECT, que genera un error Oracle asociado a la excepción NO_DATA_FOUND cuando no se encuentran filas que seleccionar, la sentencia UPDATE no genera un error Oracle si no existen filas que actualizar. Por ello, de debe usar una excepción definida por el usuario para controlar esta situación.


    Procedimiento RAISE_APPLICATION_ERROR

    Se puede utilizar el procedimiento RAISE_APPLICATION_ERROR para visualizar una excepción predefinida devolviendo un código de error no estándar y el mensaje de error. Con RAISE_APPLICATION_ERROR, se puede informar de errores en la aplicación y evitar volver excepciones no controladas.
    El procedimiento RAISE_APPLICATION_ERROR puede ser utilizado tanto en la sección ejecutable o la sección de excepción de un programa de PL/SQL o en ambas secciones. El error devuelto es coherente con la forma en que el servidor Oracle produce un error predefinido, no predefinida o definida por el usuario.

    raise_application_error (número_de_error,mensaje_error [, {TRUE | FALSE}]);
    

    En la sintaxis:
    Permite emitir mensajes de error definidos por el usuario desde subprogramas almacenados.
    Se utiliza en dos lugares diferentes:
    Sección Executable
    Sección Exception
    En la sintaxis:
    número_de_error : Es un número especificado por el usuario para la excepción entre -20000 y -20,999 (rango de número permitidos a usar).
    mensaje_error : Es el mensaje especificado por el usuario para la excepción; es una cadena de caracteres de hasta 2048 bytes de largo.
    TRUE | FALSE : Es un parámetro booleano opcional (Si es TRUE, se coloca el error en junto a los errores anteriores. Si es falso, que es el valor por defecto, el error sustituye a todos los errores anteriores.)

    DECLARE
    v_mgr     NUMBER(2):=99;
    BEGIN
      DELETE FROM employees
         WHERE  manager_id = v_mgr;
      IF SQL%NOTFOUND THEN
         RAISE_APPLICATION_ERROR(-20201,'No existe el jefe que se desea eliminar');
      END IF;
    END;
    

    En el ejemplo, su utiliza RAISE_APPLICATION_ERROR en la sección de ejecución del bloque PL/SQL para controlar si hubieron filas afectadas por la sentencia Delete. Si no se eliminaron filas, entonces se mostrará un error personalizado en formato Oracle.

    DECLARE
    v_apellido     NUMBER(2);
    BEGIN
      SELECT last_name  INTO v_apellido
         FROM employees
         WHERE  employee_id = 99;
      EXCEPTION
       WHEN NO_DATA_FOUND THEN
         RAISE_APPLICATION_ERROR (-20202,'El empleado que se consulta no es válido');
    END;
    

    En el ejemplo, su utiliza RAISE_APPLICATION_ERROR en la sección de excepción del bloque PL/SQL para controlar si la consulta retornó filas. Si no la consulta no retorna filas entonces se mostrará un error personalizado en formato Oracle.

sábado, 23 de mayo de 2015

Usando Cursores Explícitos


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.

Un programa PL/SQL abre un cursor, procesa filas devueltas por una consulta y, a continuación, cierra el cursor. El cursor marca la posición actual en el conjunto 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

-- 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;

Declarar el Cursor
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.

La sentencia CURSOR está incluida en la sección ejecutable de un bloque PL/SQL En la declaración del cursor explícito la instrucción SELECT no contiene la cláusula INTO porque sólo se está definiendo el cursor, esto es parte de la instrucción FETCH

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.


-- 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.

    -- 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;
    

    Obtener Datos del Cursor En la sintaxis: nombre_cursor: es el cursor declarado en la sección de Declaración del bloque PL/SQL. lista_de_variables o registro_PL/SQL: es donde se almacenarán los valores leídos desde el Cursor. Se deben declarar previamente y deben ser compatibles en tipo de dato con las columnas seleccionadas en la consulta. La sentencia FETCH lee los datos desde la fila actual en las variables de salida PL/SQL y avanza el puntero a la siguiente fila en el set activo. Se puede usar el atributo %NOTFOUND para chequear si el conjunto activo se ha recuperado completamente. En el bloque PL/SQL del ejemplo, el cursor cur_emp obtiene la identificación y apellido de los empleados que trabajan en el departamento 30. En la sección de Ejecución se abre el cursor, se lee la primera fila y los valores son almacenados en las variables declaras para posteriormente mostrar la información. Para poder leer todas las filas recuperadas en el cursor, se debe generar una Iteración.

    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;
    

    Obtener Datos del Cursor En el ejemplo ahora se utiliza un loop simple para leer todas las filas del cursor. Se utiliza también el atributo de cursor %NOTFOUND para la condición de salida del loop.

    -- 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;
    

    Cerrar el Cursor En la sintaxis: nombre_cursor: es el cursor declarado en la sección de Declaración del bloque PL/SQL. La sentencia CLOSE: Deshabilita (cierra) el cursor, libera el área de contexto y el set activo se elimina. Cierra el cursor después de completar el procesamiento de las sentencias FETCH. Un cursor puede ser reabierto sólo si éste está cerrado. Si se leen datos desde un cursor después de que éste se ha cerrado entonces la excepción INVALID_CURSOR se producirá. El número de cursores abiertos por sesión es determinado por el parámetro de la Base de Datos OPEN_CURSORS. Por defecto es 50.

    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.

    • %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.
    IF NOT cur_emp%ISOPEN THEN
                 OPEN cur_emp;
    END IF;
    LOOP
      FETCH cur_emp ...
    

    Atributo %ISOPEN Se pueden leer filas sólo cuando el cursor se encuentra abierto. Usar el atributo de cursor %ISOPEN antes de realizar una lectura permite validar si el cursor se encuentra abierto. Retorna el estado del cursor. TRUE si está abierto y FALSE si no. En el ejemplo, antes de leer las filas del cursor cur_emp se verifica si está abierto o no. En el caso de no estar abierto el cursor se abre.

    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;
    

    Atributos %ROWCOUNT y %NOTFOUND En el ejemplo, el bloque obtiene a todos los empleados que existen en la tabla employees. La condición para salir del loop de lectura del cursor es que las filas leída sea mayor a 10 o que ya no existan más filas que leer en el cursor.

    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;
    

    Cursores y Registros Usando el atributo %ROWTYPE se puede definir un registro basado en las columnas seleccionadas en un cursor explícito utilizando el atributo %ROWTYPE, esto significa que los nombres de los campos del registro son los nombres de columnas seleccionadas. Esto es conveniente para procesar las filas del conjunto activo, porque sólo se tienen que hacer una lectura simple en el registro. Por lo tanto, los valores de la fila leída del cursor son almacenados directamente en los campos correspondientes del registro. En el bloque del ejemplo, se define el registro reg_emp basado en las columnas seleccionadas por el cursor cur_emp (identificación y apellido de los empleados del departamento 30). Al leer las filas del cursor los valores son almacenados en los campos del registro declarado. Los nombres de Los campos del registro son los nombres de las columnas obtenidas en el cursor (employee_id y last_name).

    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.

    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; 
    
    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: Loop Simple En el bloque PL/SQL del ejemplo, se lee desde el cursor cur_emp la identificación y apellido de los empleados que trabajan en el departamento 30. La información se muestra hasta cuando no existan más filas que leer desde el cursor cur_emp.

    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.

    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; 
    
    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: WHILE LOOP En el ejemplo, se hace una primera lectura del cursor cur_emp para poder validar en la condición si existen filas que leer antes de comenzar con la iteración. Por lo tanto mientras existan filas que leer desde el cursor se mostrará la información. Cuando ya no existan más filas que leer termina la iteración del bucle WHILE LOOP y se cierra el cursor.

    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.

    FOR nombre_registro IN nombre_cursor LOOP   
     /* Procesamiento de los registros recuperados  y ejecución de sentencias*/ 
    END LOOP;
    
    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;
    

    Manejo del Cursor: FOR LOOP En el ejemplo, se lee cada una de las filas del cursor cur_emp y los valores se almacenan en el registro reg_emp cuyos campos serán employee_id y last_name que son las columnas seleccionadas en el cursor. Cuando no existen más filas que leer, el ciclo finaliza y se cierra el cursor. Declarar el registro reg_emp, abrir el cursor cur_emp, leer sus datos, finalizar el loop y cerrar el cursor se ejecutan en forma implícita utilizando FOR LOOP.

    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;
    

    Cursor FOR LOOP usando Subconsultas En este bloque PL/SQL no hay una sección declarativa. La diferencia entre el cursor FOR LOOP utilizando subconsultas y el cursor loop FOR se encuentra en la declaración de cursor. Con cursor FOR loops usando subconsultas, no es necesario declarar el cursor en la sección declarativa. Se tiene que definir la sentencia SELECT que determina el conjunto activo en el loop. El ejemplo el registro emp_record almacena los empleados que trabajan en el departamento 30 los que son obtenidos usando una subconsulta. Luego, a través del loop, se mostrará toda la información almacenada en el registro.

    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:
    • 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.
    CURSOR nombre_cursor[(nombre_parámetro tipo_dato, ...)] IS
      sentencia_select;
    
    OPEN  nombre_cursor(valor_parámetro,.....) ;
    
    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;
    

    Cursores con Parámetros El bloque del ejemplo posee un cursor con parámetro p_deptno que según el valor que se le asigne cuando se abra obtendrá información de los empleados que trabajen en el departamento asignado por parámetro. En este caso se muestran los empleados que trabajan en el departamento 30 y 20.

    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:
    • 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.
    -- 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:
  • nombre_cursor : es el nombre de un cursor declarado. El cursor debe haber sido declarado con la cláusula FOR UPDATE.
  • 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 ;
    

    Cláusula WHERE CURRENT El bloque del ejemplo obtiene las filas completas de los empleados que trabajan en el departamento 100. Al abrir el cursor FOR UPDATE se bloquean todas sus filas en la tabla (las filas que poseen el departamento 100). Después de cerrar el cursor, con la instrucción COMMIT se liberan las filas recuperadas por el cursor en la tabla original.

    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).

    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.

    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;
    

Escribiendo Estructuras de Control

Control de Flujo de Ejecución

Se puede cambiar la ejecución lógica de las sentencias dentro de un bloque PL/SQL a través de Estructuras de Control. Una estructura de control consta de un punto de entrada y uno de salida.

Las Estructuras de Selección: verifican cierta condición, después ejecutan cierta secuencia de expresiones dependiendo si la condición resultó ser verdadera o falsa. Una condición es cualquier variable o expresión que retorna un valor booleano (TRUE, FALSE o NULL).

Expresiones CASE: retornan un resultado basado en una o más alternativas.

Sentencias CASE: evalúan una condición y realiza una acción.

Las Estructuras de Iteración: ejecutan una secuencia de sentencias repetidamente mientras la condición permanezca verdadera.


Sentencia IF

La estructura de control de PL/SQL IF es similar a la estructura IF de otros lenguajes procedurales. Permiten realizar acciones de forma selectiva basado en la evaluación de condiciones.

En la Sintaxis:

  • condición: es una variable o expresión booleana que retorna TRUE, FALSE o NULL.
  • THEN: Asocia la expresión Booleana con la secuencia de instrucciones que continúan.
  • sentencias: puede ser una o más sentencias PL/SQL o SQL. Las sentencias son ejecutadas sólo si la condición es evaluada como TRUE.
  • ELSIF: Es una palabra que introduce condiciones adicionales si la primera condición retorna FALSE o NULL.
  • ELSE: Introduce la cláusula por defecto que es ejecutada sólo si ninguno de las condiciones anteriores es TRUE.
  • END IF: Marca el fin de una sentencia IF.
DECLARE
  v_mi_edad number(2) := 31;
BEGIN
  IF v_mi_edad < 11 THEN
      DBMS_OUTPUT.PUT_LINE('Yo soy un niño');  
  END IF;
END;

DECLARE
  v_mi_edad       number(2) := 10;
  v_mi_nombre   varchar2(20) := 'Luis';
BEGIN
  IF v_mi_edad < 11  AND  v_mi_nombre = 'Luis' THEN
     DBMS_OUTPUT.PUT_LINE('Yo soy un niño que se llama Luis');
  END IF;
END;

Sentencia IF Simple En el bloque PL/SQL del primer ejemplo, la variable v_mi_edad es inicializada en 31. La condición de la sentencia IF retorna FALSE, porque v_mi_edad no es menor que 11 por lo tanto el control nunca alcanza la cláusula THEN sino que la ejecución del bloque continúa después del END IF.

En el bloque del segundo ejemplo, la condición usa el operador AND por lo tanto, por lo tanto la condición completa es evaluada como TRUE sólo si ambas condiciones son evaluadas como TRUE. Por lo tanto en este caso como ambas condiciones son verdaderas (TRUE) el control alcanza la cláusula THEN y el bloque muestra el mensaje Yo soy un niño que se llama Luis

DECLARE
  v_salario_min   NUMBER(4);
BEGIN
  SELECT MIN(SALARY) INTO v_salario_min
     FROM employees;
  DBMS_OUTPUT.PUT_LINE('El salario mínimo es de : ' || TO_CHAR(v_salario_min,'$999,999'));
  IF v_salario_min > 3000 THEN
      DBMS_OUTPUT.PUT_LINE('Por lo tanto está dentro del rango normal');
  ELSE
      DBMS_OUTPUT.PUT_LINE('Por lo tanto debe ser aumentado'); 
  END IF;
END;

Sentencia IF THEN ELSE En el bloque PL/SQL, se obtiene el salario mínimo entre todos lo empleados el que se visualiza una vez obtenido. Posteriormente se ejecuta la condición de la sentencia IF la que retorna FALSE porque v_salario_min no es mayor a 3000 por lo tanto el control pasa a la cláusula ELSE y se mostrará el mensaje Por lo tanto debe ser aumentado.

DECLARE
  v_mi_edad number(2) := 30;
  v_mi_nombre varchar2(20) := 'Luis';
BEGIN
  IF v_mi_edad < 11 AND v_mi_nombre = 'Luis' THEN
     DBMS_OUTPUT.PUT_LINE('Yo soy un niño que se llama Luis');
  ELSE
     DBMS_OUTPUT.PUT_LINE('Yo no soy un niño pero me llamo Luis');
  END IF;
END;

Sentencia IF THEN ELSE En el bloque PL/SQL del ejemplo, la condición usa el operador AND por lo tanto, esta condición es evaluada como TRUE sólo si ambas condiciones son evaluadas como TRUE. En este caso como sólo una de las condiciones es verdadera el control pasa a la instrucción ELSE y el bloque muestra el mensaje Yo no soy un niño pero me llamo Luis

DECLARE
v_mi_edad  number(2) :=31;
BEGIN
   IF v_mi_edad  < 20  THEN
        DBMS_OUTPUT.PUT_LINE('Yo soy un niño');
   ELSIF v_mi_edad < 30 THEN
       DBMS_OUTPUT.PUT_LINE('Yo estoy en mis veintes');
   ELSIF v_mi_edad < 40 THEN
       DBMS_OUTPUT.PUT_LINE('Yo estoy en mis treintas');
   ELSE
      DBMS_OUTPUT.PUT_LINE('Yo siempre seré joven');
   END IF;
END;

Sentencia IF THEN ELSE El bloque PL/SQL del ejemplo, valida el valor de la variable v_mi_edad. La cláusula IF contiene múltiples cláusulas ELSIF y un ELSE. La condición para ELSIF son seguidas por la cláusula THEN que es ejecutada si la condición del ELSIF retorna TRUE. Cuando se tienen múltiples cláusulas ELSIF, si la primera condición es FALSE o NULL el control pasa a la siguiente cláusula ELSIF hasta que alguna de ellas retorne TRUE. Las condiciones son evaluadas una a una. Si todas son FALSE o NULL se ejecutarán las sentencias de la cláusula ELSE si existe.

DECLARE
v_sal_max  NUMBER(5);
BEGIN
   SELECT MAX(salary) INTO v_sal_max
      FROM employees;
   IF v_sal_max  < 5000  THEN
        DBMS_OUTPUT.PUT_LINE('Salario máximo menor a 5000');
   ELSIF v_sal_max < 10000  THEN 
       DBMS_OUTPUT.PUT_LINE('Salario máximo menor a 10000 y mayor = a 5000');
   ELSIF v_sal_max < 15000 THEN
       DBMS_OUTPUT.PUT_LINE('Salario máximo menor a 15000 y mayor = a 10000');
   ELSIF v_sal_max < 20000 THEN
       DBMS_OUTPUT.PUT_LINE('Salario máximo menor a 20000 y mayor = a 15000');
   ELSE
      DBMS_OUTPUT.PUT_LINE('Salario máximo es mayor a 20000');
   END IF;
END;

Sentencia IF THEN ELSE En el ejemplo, se valida el salario máximo obtenido desde tabla employees. Las cláusulas son evaluadas una a una hasta que alguna de ellas retorne TRUE. Si todas la condiciones son FALSE o NULL se ejecutarán las sentencias de la cláusula ELSE (si existe). En el ejemplo, el salario máximo es 24100 por lo tanto el bloque mostrará el mensaje Salario máximo es mayor a 20000 de la cláusula ELSE.

DECLARE
v_mi_edad number;
BEGIN
   IF v_mi_edad < 11 THEN
       DBMS_OUTPUT.PUT_LINE('Yo soy un niño ');  
   ELSE
       DBMS_OUTPUT.PUT_LINE('Yo no soy un niño ');
   END IF;
END;

Valores Nulos en Sentencias IF En el bloque PL/SQL, la variable v_mi_edad es declarada pero no inicializada. La condición en la sentencia IF retorna NULL (en lugar de TRUE o FALSE) por lo tanto, en este caso, el control se entrega a la sentencia ELSE visualizando el bloque el mensaje Yo no soy un niño.

Expresiones CASE

Una expresión CASE retorna un resultado basado en una o más alternativas. Para devolver el resultado, la expresión CASE utiliza un selector, que es una expresión cuyo valor se utiliza para devolver una de varias alternativas. El selector es seguido por una más cláusulas WHEN que se comprueban secuencialmente. El valor del selector determina qué resultado se devuelve. Si el valor del selector es igual al valor de una expresión de la cláusula WHEN, la cláusula WHEN se ejecuta y que se retorna el resultado.

CASE [ selector ]
   WHEN expresión1 THEN resultado1
   WHEN expresión2 THEN resultado2
   ...
   WHEN expresiónN THEN resultadoN
  [ ELSE resultadoN+1 ]
END;
En la sintaxis:
  • Para retornar el resultado, la expresión CASE usa un selector y una expresión cuyo valor es usado para retornar uno de las diferentes alternativas.
  • El selector es seguido de una o más cláusulas WHEN que son chequeadas secuencialmente.
  • El valor del selector determina que resultado se retorna.
  • Si el valor del selector es igual al valor de la expresión de la cláusula WHEN ésta es ejecutada y el resultado correspondiente es retornado.
  • Las cláusulas WHEN contiene condiciones de búsqueda que devuelven un valor Booelano.
DECLARE
v_calidad         varchar2(1) := 'A';
v_valoracion  varchar2(20);
BEGIN
   v_valoracion := 
      CASE v_calidad
         WHEN 'A' THEN ' Excelente'
         WHEN 'B' THEN ' Muy bueno'
         WHEN 'C' THEN ' Bueno'
         ELSE 'No existe calidad'
      END;
DBMS_OUTPUT.PUT_LINE ('Calidad: '|| v_calidad || ' Valoración:' || v_valoracion);
END;

Expresiones CASE En el bloque del ejemplo, el selector v_calidad determina el valor que la variable v_valoracion tendrá. En este caso, será el valor ‘Excelente’, ya que la variable calidad se ha inicializa en ‘A’.

DECLARE
v_salario_prom  NUMBER(5);
v_nombre_dep  VARCHAR2(30);
v_valoracion     VARCHAR2(15);
BEGIN
     SELECT ROUND(AVG(emp.salary)), dep.department_name INTO v_salario_prom, v_nombre_dep
        FROM employees emp JOIN departments dep
              ON(emp.department_id = dep.department_id)
       WHERE emp.department_id = 50
      GROUP BY dep.department_name ;
     v_valoracion := 
     CASE
         WHEN v_salario_prom > 5000  THEN ' Exelente'
         WHEN v_salario_prom > 3000  THEN ' Bueno'
         ELSE 'Regular'
     END;
DBMS_OUTPUT.PUT_LINE ('El salario promedio del departamento 50 ' || v_nombre_dep || ' es de ' || TO_CHAR(v_salario_prom, '9,999') || ' calificado de ' || v_valoracion);
END;

Expresiones CASE En el ejemplo, no existe un selector ya que en este caso las cláusulas WHEN contienen una expresión de testeo que retornará un valor booleano. Cuando la expresión de testeo o búsqueda retorne TRUE su resultado será asignado a la variable v_valoracion según el salario promedio.

DECLARE
   v_deptid            NUMBER(3):=30;
   v_deptnom       VARCHAR2(20);
   v_total_emp     NUMBER(5);
BEGIN
  CASE  v_deptid
   WHEN  30 THEN
         SELECT COUNT(employee_id),  department_name 
         INTO v_total_emp, v_deptnom 
         FROM employees JOIN departments        
         USING (department_id)
         WHERE department_id = v_deptid
         GROUP BY department_name ;
  END CASE;
DBMS_OUTPUT.PUT_LINE ('En el departamento '|| v_deptnom|| ' trabaja un total de ' || v_total_emp || ' empleados');
END;

Sentencias CASE En el bloque, se valida el valor de la variable v_deptid. Si el valor es 30 entonces se obtiene el total de empleados que trabajan en ese departamento y el nombre del departamento para posteriormente visualizar el mensaje de salida.

DECLARE
 v_sal_prom    NUMBER(5);   
BEGIN
  SELECT ROUND(AVG(salary))
  INTO v_sal_prom
  FROM employees;
  CASE 
      WHEN  v_sal_prom < 5000 THEN
         UPDATE employees
                 SET salary = salary * 1.25
           WHERE salary < v_sal_prom;
      WHEN v_sal_prom < 7000 THEN
          UPDATE employees
                 SET salary = salary * 1.10
           WHERE salary < v_sal_prom;
      ELSE
           DBMS_OUTPUT.PUT_LINE('No corresponde aumento de salario');
  END CASE;
END;

Sentencias CASE En el ejemplo, el bloque obtiene el salario promedio entre todos los empleados. La sentencia CASE evalúa el valor del salario promedio obtenido para actualizar el salario a los empleados que corresponda según la condición dada.

Diferencias entre CASE

Expresiones CASE: Evalúan la condición y retorna un valor. Terminan con END.

Sentencias CASE: Evalúan la condición y realiza una acción. La sentencia puede ser un bloque completo PL/SQL. Terminan con END CASE.

Manejo de Valores Nulos

Las comparaciones simples que involucran nulos siempre retornan NULL.

Para preguntar si una variable no posee un valor NULO se debe usar el operador lógico NOT (NOT NULL).

En sentencias de control condicionales, si la condición retorna NULL, la secuencia de sentencias asociadas no son ejecutadas.

DECLARE
x  NUMBER:= 5;
y  NUMBER:= NULL;
BEGIN
 IF x != y THEN
    DBMS_OUTPUT.PUT_LINE('x es distinto de y');
 ELSE
    DBMS_OUTPUT.PUT_LINE('x es distinto de y pero la comparación es NULL');  
 END IF;
END;

Manejo de Valores Nulos En el bloque PL/SQL, la condición de comparación entre las variables x e y retorna NULL no TRUE, por lo tanto la secuencias de sentencias después del THEN no serán ejecutadas, se ejecutarán las sentencias después del ELSE.

DECLARE
a  NUMBER:= NULL;
b  NUMBER:= NULL;
BEGIN
 IF a = b THEN
    DBMS_OUTPUT.PUT_LINE('a es igual a b');
 ELSE
    DBMS_OUTPUT.PUT_LINE('a y b son NULOS pero la comparación es NULL NO TRUE');  
 END IF;
END;

Manejo de Valores Nulos En el bloque PL/SQL, la condición retorna NULL no TRUE, por lo tanto la secuencias de sentencias después del THEN no serán ejecutadas, se ejecutarán las sentencias después del ELSE.

DECLARE                                                                                                                      
 a number:= NULL;                                                                                                       
 b number:=NULL;                                                                                                         
BEGIN                                                                                                                            
    IF a IS NOT NULL AND b IS NOT NULL THEN                                                   
           DBMS_OUTPUT.PUT_LINE('a y b no son nulos');                                          
    ELSIF                                                                                                                               
         a IS NULL AND b IS NULL THEN                                                                      
           DBMS_OUTPUT.PUT_LINE('a y b son nulos');                                              
    END IF;                                                                                                                           
END;

Manejo de Valores Nulos En la condición del bloque PL/SQL se pregunta si los valores de las variables son Nulos o no. Como ambas variables han sido inicializadas NULAS la sentencia que se ejecutará es la que se especifica a continuación de la instrucción THEN del ELSIF.

Controles de Iteración (Bucles)

PL/SQL ofrece varias facilidades para estructurar bucles y poder repetir una instrucción o secuencia de instrucciones varias veces. Los loops se utilizan principalmente para ejecutar instrucciones de forma repetida hasta que se alcanza una condición de salida. Es obligatorio tener una condición de salida en un loop, de lo contrario, es infinito.

Las construcciones de loop o bucle son el segundo tipo de estructura de control. PL/SQL proporciona los siguientes tipos de loops:

  • Loop Básico que realiza acciones repetitivas sin condiciones generales.
  • FOR loops que realizan acciones iterativas basadas en un recuento.
  • WHILE loops que realizan acciones iterativos basados en una condición.

El comando EXIT se puede utilizar para terminar loops. Un loop básico debe tener una salida.

Loop Básico

La forma más simple de una sentencia LOOP es el Loop Básico, que encierra una serie de declaraciones entre las palabras reservadas LOOP y END LOOP . Cada vez que el flujo de ejecución llega a la instrucción END LOOP se devuelve el control a la sentencia LOOP correspondiente. Un loop básico permite la ejecución de sus sentencias al menos una vez, incluso si la condición de salida ya se cumple al entrar en el loop .

Sin la instrucción EXIT, el loop sería infinito . Se puede usar la sentencia EXIT para terminar el loop. El control pasa a la siguiente sentencia después de la sentencia END LOOP. Si a la sentencia EXIT se agrega una Condición WHEN significa que el LOOP se ejecutará hasta que se cumpla la condición. Un loop básico puede contener múltiples sentencias EXIT, pero se recomienda que sólo tener un punto de salida.

En la sintaxis:
  • sentencia: pueden ser uno o más sentencias PL/SQL o SQL.
  • condición: es una variable Booleana o expresión
DECLARE
  v_countryid    locations.country_id%TYPE := 'CA';
  v_loc_id          locations.location_id%TYPE;
  v_contador     NUMBER(2) := 1;
  v_new_city     locations.city%TYPE := 'Montreal';
BEGIN
    SELECT MAX(location_id) 
         INTO v_loc_id 
       FROM locations
    WHERE country_id = v_countryid;
    LOOP
        INSERT INTO locations(location_id, city, country_id)   
        VALUES((v_loc_id + v_contador), v_new_city, v_countryid);
        v_contador := v_contador + 1;
        EXIT WHEN v_contador > 3;
    END LOOP;
END;

Loop Básico El bloque PL/SQL del ejemplo inserta nuevas filas a la tabla locations. La inserción de nuevas filas se efectuará 3 veces ya que el LOOP es controlado a través de la variable contador. Cuando esta variable sea mayor a 3 el control pasará a la sentencia que continúa después del END LOOP, en este caso se acaba el bloque PL/SQL.

WHILE condición LOOP
  sentenciaN;
END LOOP;

WHILE Loop Se puede utilizar el WHILE Loop para repetir una secuencia de sentencias hasta que la condición de control no sea verdadera. La condición se evalúa al inicio de cada iteración. El ciclo termina cuando la condición es FALSE o NULL. Si la condición es FALSE o NULL en el inicio del loop, no se realizan más iteraciones. Por lo tanto, es posible que ninguna de las instrucciones dentro del loop se ejecuta. En la sintaxis: condición: es una variable Booleana o expresión. sentencia: pueden ser uno o más sentencias PL/SQL o SQL.

DECLARE
  v_countryid   locations.country_id%TYPE := 'CA';
  v_loc_id         locations.location_id%TYPE;
  v_new_city    locations.city%TYPE := 'Montreal';
  v_contador    NUMBER := 1;
BEGIN
  SELECT MAX(location_id) 
       INTO v_loc_id 
     FROM locations
   WHERE country_id = v_countryid;
  WHILE v_contador <= 3 LOOP
      INSERT INTO locations(location_id, city, country_id)   
      VALUES((v_loc_id + v_contador), v_new_city, v_countryid);
      v_contador:= v_contador + 1;
  END LOOP;
END;

WHILE Loop Basados en el ejemplo anterior, ahora el ciclo iterativo es manejado usando WHILE LOOP. La condición ahora es evaluada al comienzo de cada iteración a través de la variable contador. Cuando esta variable sea mayor a 3 el control pasará a la sentencia que continúa después del END LOOP.

FOR contador IN [REVERSE] 
    límite_inferior .. límite_superior LOOP  
    sentenciaN;
END LOOP;

FOR Loop FOR Loop tiene la misma estructura general que el Loop básico. Además, tienen una instrucción de control antes de la palabra clave LOOP para establecer el número de iteraciones que realiza PL/SQL. Las iteraciones se efectúan un número finito y conocido de veces. El contador es declarado implícitamente y sólo pude ser usado dentro del loop. La sintaxis ‘límite_inferior .. límite_superior’ es obligatoria. Pueden ser literales, variables o expresiones pero que deben ser números enteros. En la sintaxis: contador: es un entero declarado implícitamente (sólo involucra al loop y posteriormente se elimina) cuyo valor incrementa o disminuya de manera automática en uno en cada iteración del loop hasta que se alcanza el límite_inferiror o límite_superior. REVERSE: causa que el contador disminuya con cada iteración desde el límite_superior al límite_inferior. límite_inferior: específica el valor inferior del contador. límite_superior: especifica el valor superior del contador

DECLARE
  v_countryid   locations.country_id%TYPE := 'CA';
  v_loc_id          locations.location_id%TYPE;
  v_new_city     locations.city%TYPE := 'Montreal';
BEGIN
  SELECT MAX(location_id) 
       INTO v_loc_id 
     FROM locations
   WHERE country_id = v_countryid;
   FOR i IN 1..3 LOOP
        INSERT INTO locations(location_id, city, country_id)   
        VALUES((v_loc_id + i), v_new_city, v_countryid );
   END LOOP;
END;

FOR Loop En el bloque ahora el ciclo iterativo se maneja con FOR LOOP. La variable i es declara implícitamente para ser usada en el loop y su valor irá desde el límite inferior (1) al límite superior (3) que indica la cantidad de iteraciones del loop. Una vez que se haya llegado al valor_máximo del loop el control pasará a la sentencia que continúa después del END LOOP.

DECLARE
  v_countryid   locations.country_id%TYPE := 'CA';
  v_loc_id          locations.location_id%TYPE;
  v_new_city     locations.city%TYPE := 'Montreal';
  v_limite_inf    NUMBER(1):=1;
  v_limite_sup  NUMBER(1):=3;
BEGIN
  SELECT MAX(location_id) 
       INTO v_loc_id 
     FROM locations
   WHERE country_id = v_countryid;
   FOR i IN v_limite_inf .. v_limite_sup LOOP
        INSERT INTO locations(location_id, city, country_id)   
        VALUES((v_loc_id + i), v_new_city, v_countryid );
   END LOOP;
END;

FOR Loop Basados en el mismo ejemplo anterior, en este bloque PL/SQL el límite inferior (1) y el límite superior (3) del loop está dado por las variables v_limite_inf y v_limite_sup respectivamente. Una vez que se haya llegado al valor_máximo del loop el control pasará a la sentencia que continúa después del END LOOP.

Pautas para Loops

Usar el control de iteración LOOP básico cuando las sentencias contenidas dentro del bucle deben ser ejecutas a lo menos una vez.

Usar WHILE Loop si la condición tiene que ser evaluada al comienzo de cada iteración.

Usar FOR Loop si el número de iteraciones es conocido.

Loops Anidados y Etiquetas

  • Los loops anidados tienen múltiples niveles.
  • El uso de etiquetas permiten distinguir entre bloques y loops.
  • Se pueden anidar loops básicos, FOR, While dentro de otro.
  • La etiqueta debe ir antes de la palabra LOOP palabra dentro de delimitadores de la etiqueta (la etiqueta << >>). En loops FOR o WHILE coloque la etiqueta antes de FOR o WHILE.

En el bloque del ejemplo hay dos ciclos iterativos. El loop externo es identificado con la etiqueta <> el loop interno por la etiqueta <>. El loop externo inicialmente se ejecutará 10 veces (controlado por la cláusula EXIT WHEN v_contador > 10) y el loop interno se ejecutará 1 vez (controlado por la cláusula EXIT WHEN v_inner_done = 'YES') pero además en el loop interno se controla la salida del loop principal a través de la cláusula EXIT v_loop_externo WHEN total_done = 'YES'

Sentencia PL/SQL CONTINUE

La sentencia CONTINUE permite transferir el control dentro de un loop a una nueva iteración o para salir del loop. Muchos otros lenguajes de programación tienen esta funcionalidad. Con Oracle 11g, PL/SQL también ofrece esta funcionalidad. En versiones anteriores a Oracle 11g, se puede codificar una solución utilizando variables booleanas e instrucciones condicionales para simular la funcionalidad de CONTINUE.

La sentencia CONTINUE ofrece un medio sencillo para controlar las iteraciones de loop. Puede ser más eficiente que soluciones de codificación anteriores. La sentencia CONTINUE no puede aparecer fuera de un loop porque esto genera un error del compilador.

DECLARE
  v_total SIMPLE_INTEGER := 0;
BEGIN
  FOR i IN 1..10 LOOP
    v_total := v_total + i;
    dbms_output.put_line('Total al comienzo del Loop es: '|| v_total);
    CONTINUE WHEN i > 5;
    v_total := v_total + i; 
    dbms_output.put_line('Total despues de CONTINUE es: '|| v_total);    
  END LOOP;
END;

Sentencia PL/SQL CONTINUE La primera asignación a la variable v_total es ejecutada para cada uno de los 10 iteraciones del loop. La segunda asignación a la variable v_total se ejecuta durante las primeros cinco iteraciones del loop. La sentencia CONTINUE transfiere el control a una nueva iteración del loop cuando i sea mayor a 5, por lo que durante las cinco últimas iteraciones del loop, la segunda asignación a la variable v_total no se ejecuta. El resultado final de la variable v_total es 70.

DECLARE 
 v_total NUMBER := 0;
BEGIN
 <>
 FOR i IN 1..10 LOOP
   v_total := v_total + 1;
   dbms_output.put_line('Total es: ' || v_total);
   FOR j IN 1..10 LOOP
     CONTINUE LoopPrincipal WHEN i + j > 5;
     v_total := v_total + 1;
   END LOOP;
 END LOOP;
END;

Sentencia PL/SQL CONTINUE Se puede utilizar la instrucción CONTINUE para saltar a la siguiente iteración de un loop exterior. Para esto, el loop exterior debete tener etiqueta para identificar dónde la sentencia CONTINUE debe pasar el control. La sentencia CONTINUE termina el loop interno cuando la condición WHEN es verdadera (al igual que si se usara EXIT). Después de que el loope más interior es terminado por la sentencia CONTINUE, el control se transfiere a la siguiente iteración del loop más externo que en el ejemplo está marcado con la etiqueta LoopPrincipal. Cuando este par de loop se completa, el valor de la variable v_total es 20. También se puede utilizar la instrucción CONTINUE dentro de un bloque interno que no contiene un loop mientras el bloque esté anidado dentro de un loop externo.

Interactuando con el Servidor Oracle

Sentencias SQL en Bloques PL/SQL

Cuando se consulta información o se efectúan cambios a la Base de Datos se debe usar SQL. PL/SQL soporta el Lenguaje de Manipulación de datos
(DML) y los comandos del control de transacciones de SQL. En un bloque PL/SQL se pueden recuperar una o varias filas desde la Base de
Datos utilizando el comando SELECT. Para realizar cambios a una ó varias filas de la Base de Datos en un bloque PL/SQL se deben utilizar los
comandos DML INSERT, UPDATE, DELETE y/o MERGE. Para controlar una transacción en el bloque PL/SQL de debe utilizar el comando
COMMIT, ROLLBACK ó SAVEPOINT.

PL/SQL no admite directamente el uso del lenguaje de definición de datos (DDL), como CREATE TABLE, ALTER TABLE o DROP TABLE. Estas sentencias
pueden ejecutar a través de SQL Dinámico.

PL/SQL no admite directamente del lenguaje de control de datos (DCL), como las sentencias GRANT o REVOKE. Para ejecutar estas sentencias se puede
utilizar SQL dinámico.

SELECT  lista_seleccionar
INTO  {nombre_variable [, nombre_variable] ...
                | nombre_registro}  
FROM  tabla
[WHERE  condición];

Sentencia SELECT en PL/SQL

Se debe utilizar la sentencia SELECT para recuperar datos de la base de datos.

En la Sintaxis:

  • lista_seleccionar : lista de a lo menos una columna y puede incluir expresiones SQL, funciones de columnas o funciones de grupo.
  • nombre_variable : variable escalar que almacenará el valor recuperado.
  • nombre_registro : es un registro PL/SQL que almacenará el valor recuperado.
  • tabla : especifica el nombre de tabla de la Base de Datos.
  • condición : está compuesta de nombres de columnas, expresiones, constantes y operadores de comparaciones.

  • Las consultas deben retornar sólo una fila cuando se utilizan variables escalares.
  • La cláusula INTO es requerida ya que los datos que retorna la sentencia SELECT deben ser almacenados en variables.
  • Se debe especificar el mismo número de variables en la cláusula INTO como columnas de Base de Datos en la cláusula SELECT. Además sus tipos de datos deben ser compatibles.
  • La cláusula WHERE es opcional y se puede utilizar para especificar variables de entrada, constantes, literales y expresiones PL/SQL. Sin embargo, cuando se utiliza la cláusula INTO, se debe buscar una sola fila; en estos casos se requiere utilizar la cláusula WHERE.
  • Cuando una sentencia SELECT en PL/SQL retorna más de una fila o no retorna filas se genera un error. Esos errores son excepciones estándares que pueden ser incorporadas en la sección de excepciones. Estas pueden ser NO_DATA_FOUND y TOO_MANY_ROWS.
  • Una sentencia SELECT con la cláusula INTO puede recuperar sólo una fila a la vez. Si se requiere recuperar múltiples filas y operar sobre los datos, entonces se puede hacer uso de Cursores Explícitos.
DECLARE
 v_fname VARCHAR2(25);
BEGIN
     SELECT first_name 
     INTO v_fname 
     FROM employees 
     WHERE employee_id  > 200;
     DBMS_OUTPUT.PUT_LINE('El primer nombre del empleado 200 es : '|| v_fname);
END;

El bloque PL/SQL, obtiene el primer nombre de los empleados con identificación mayor a 200. Esto visualiza el error ORA-01422 ya que la sentencia retorna más de una fila y una sentencia SELECT con cláusula INTO debe retornar una fila. La opción es utilizar una comparación que asegure el retorno de una fila o utilizar un cursor explícito.

DECLARE
 v_fname VARCHAR2(25);
BEGIN
     SELECT first_name 
     INTO v_fname 
     FROM employees 
     WHERE employee_id=200;
     DBMS_OUTPUT.PUT_LINE('El primer nombre del empleado 200 es : '|| v_fname);
END;

El bloque PL/SQL anterior ahora obtiene el primer nombre del empleado 200 y se almacena en la variable v_fname para posteriormente mostrarlo.

DECLARE
 v_contrato   employees.hire_date%TYPE;
 v_salario    employees.salary%TYPE;  
BEGIN
   SELECT   hire_date, salary
        INTO   v_contrato, v_salario
      FROM   employees
   WHERE   employee_id = 100;  
  DBMS_OUTPUT.PUT_LINE('La fecha de contrato del empleado 100 es: ' || v_contrato);
  DBMS_OUTPUT.PUT_LINE('El salario del empleado 100 es: ' || v_salario);
END;

En el ejemplo, el bloque recupera la fecha de contrato y salario del empleado 100.

DECLARE    
   v_sum_sal  NUMBER(10,2); 
   v_deptno   NUMBER NOT NULL := 60;           
BEGIN
   SELECT  SUM(salary)
        INTO v_sum_sal FROM employees
    WHERE  department_id  > v_deptno;
   DBMS_OUTPUT.PUT_LINE ('La suma de los salarios es ' || TO_CHAR(v_sum_sal,
   '$999,999'));
END;

En el bloque PL/SQL, se obtiene la sumatoria de los salarios de todos los empleados que trabajan en el departamento 60.

DECLARE
  v_hire_date        employees.hire_date%TYPE;
  v_fecha_actual  v_hire_date%TYPE;
  employee_id      employees.employee_id%TYPE := 176;        
BEGIN
   SELECT hire_date, sysdate
   INTO    v_hire_date,  v_fecha_actual
   FROM    employees
   WHERE employee_id = employee_id;        
END;

En el bloque PL/SQL, se recupera la fecha de contrato del empleado 176 desde tabla employees además de la fecha actual del sistema. Sin embargo se produce un error porque en la cláusula WHERE, el nombre de la variable PL/SQL es el mismo nombre de la columna en la tabla employees por lo tanto Oracle asume que las dos ocurrencias de employee_id en la cláusula WHERE se refieren a la columna de la Base de Datos y retorna TODAS las filas de la tabla, en este caso 107 filas.


Convenciones de Nombres

  • Utilizar las convenciones de nombres de variables o identificadores para evitar ambigüedades en la cláusula WHERE.
  • Evitar usar nombre de columnas de Base Datos como identificadores.
  • Los nombres de columnas de tablas de la Base de Datos tienen precedencia por sobre los nombres de variables locales.
  • Los nombres de variables locales y parámetros formales tienen precedencia por sobre los nombres de tabla de la Base de Datos.
  • La posibilidad de ambigüedad de nombres en la cláusula SELECT no es posible porque cualquier identificador en la cláusula SELECT debe ser una columna de Base de Datos.
  • Tampoco es ambiguo en la cláusula INTO porque los identificadores en la cláusula INTO deben ser variables PL/SQL. Esta confusión sólo es posible en la cláusula WHERE.

Manipulación de Datos en PL/SQL

Se manipulan los datos en la base de datos mediante el uso de los comandos DML. Se pueden ejecutar comandos DML de INSERT, UPDATE, DELETE y MERGE sin restricciones en PL/SQL. Los bloqueos de fila (y bloqueos de tabla) se liberan mediante la inclusión de COMMIT O ROLLBACK en las sentencias del bloque PL/SQL.

  • La sentencia INSERT agrega nuevas filas a la tabla.
  • La sentencia UPDATE modifica filas existentes en la tabla.
  • La sentencia DELETE elimina filas de la tabla.
  • La sentencia MERGE selecciona las filas de una tabla a actualizar o insertar en otra tabla. La decisión de actualizar o insertar en la tabla de destino se basa en una condición en la cláusula ON.
-- Ejemplo 1

BEGIN
    INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, 
                                              job_id, salary)
     VALUES(employees_seq.NEXTVAL, 'Ruth', 'Cores','RCORES', sysdate, 
                    'AD_ASST', 4000);
END;

-- Ejemplo 2

CREATE TABLE bono
(id_empleado     NUMBER(6),
 bono                  NUMBER(8,2));

BEGIN
    INSERT INTO bono
    (SELECT employee_id, ROUND(salary * 0.20)
        FROM employees);
END;

Inserción de Datos en PL/SQL
En el primer ejemplo, el bloque PL/SQL agrega información de un nuevo empleado a la tabla employees.
En el segundo ejemplo, se crea la tabla BONO la que es utilizada en el bloque PL/SQL para insertar información obtenida desde la tabla EMPLOYEES. En este caso es la identificación del empleado y el 20% del salario valores que serán almacenados en las columnas id_empleado y bono de la tabla BONO respectivamente.

-- Ejemplo 1

DECLARE     
  v_sal_incrementado   employees.salary%TYPE := 800;   
BEGIN
  UPDATE employees
         SET salary = salary + v_sal_incrementado
   WHERE job_id = 'ST_CLERK';
END;

-- Ejemplo 2
DECLARE
  v_depto   employees.department_id%TYPE := 10; 
BEGIN       
  DELETE FROM   employees
   WHERE department_id = v_depto;
END;

Modificación de Datos en PL/SQL
En el bloque PL/SQL del primer ejemplo, se incrementa el salario actual en 800 de todos los empleados cuyo trabajo es ST_CLERK.
En el bloque PL/SQL del segundo ejemplo, se eliminan, desde tabla employees, las filas de los empleados que pertenezcan al departamento 10.

CREATE TABLE copia_emp AS SELECT * FROM employees;
TRUNCATE TABLE copía_emp;

DECLARE
BEGIN
    MERGE INTO copia_emp c
    USING employees e
          ON (e.employee_id = c.empno)
    WHEN MATCHED THEN
    UPDATE SET
       c.first_name     = e.first_name,
       c.last_name      = e.last_name,
       c.department_id  = e.department_id
     WHEN NOT MATCHED THEN
     INSERT VALUES(e.employee_id, e.first_name, e.last_name,
                   e.email, e.phone_number, e.hire_date, e.job_id, e.salary, e.commission_pct, e.manager_id, e.department_id);
END;

Combinación de Datos en PL/SQL
Las instrucción MERGE inserta o actualiza las filas de una tabla usando los datos de otra tabla. Cada fila se inserta o se actualiza en la tabla de destino en función de una condición de unión de igualdad (Join). El ejemplo, el bloque PL/SQL agrega o actualiza filas en la tabla copia_emp a partir de los datos que existen en tabla employees (la tabla copia_emp primero se crear con las misma estructura y datos de tabla employees y luego ser trunca para poder ser utilizada en el bloque PL/SQL). La coincidencia que se busca es que el valor de la columna employee_id existan en la tabla copia_emp al (en la columna empno). Si se encuentra una coincidencia, la fila se actualiza para que coincida con la fila de la tabla empleados. Si no se encuentra la fila de la tabla employees se inserta en la tabla copia_emp.


Control de Transacciones en PL/SQL

Una transacción es una serie de sentencias SQL de manipulación de datos que provee una unidad lógica de trabajo. Las sentencias de control de transacciones permiten asegurar la consistencia de la Base de Datos. Las sentencias de control de transacciones usadas son COMMIT, ROLLBACK y SAVEPOINT.

Las sentencias que se ejecutan después del último COMMIT o ROLLBACK comprenden la transacción (o grupo de transacciones activas o actual). De forma implícita se inicia una nueva transacción después de un COMMIT o ROLLBACK. La sentencia COMMIT finaliza la transacción actual y efectúa los cambios en la Base de Datos en forma permanente.

La sentencia ROLLBACK finaliza la transacción actual y deshace todos los cambios realizados en la Base de Datos por la transacción actual. Con la sentencia SAVEPOINT es posible nombrar y marcar un punto determinado donde se podrá retornar el control luego de ejecutarse una sentencia ROLLBACK.

BEGIN
   UPDATE employees
          SET salary = salary + 100
     WHERE employee_id = 100;
   DELETE FROM job_history
    WHERE employee_id = 101;
   SAVEPOINT inserta;
   INSERT INTO departments
   VALUES(departments_seq.NEXTVAL, 'DEPTO NUEVO', '110', 1700);
   ROLLBACK TO inserta;
   COMMIT;
END;

Control de Transacciones en PL/SQL
En el en el bloque del ejemplo, se “marcan” las instrucciones que insertan una nueva fila a tabla departments con el nombre inserta. Por lo tanto el rollback se efectúa sólo a la sentencia INSERT y COMMIT afectará a las sentencias UPDATE y DELETE del bloque PL/SQL.


Cursor SQL

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.

Atributos de Cursor SQL para Cursores Implícitos

Los atributos de cursor de SQL permiten evaluar lo que sucedió cuando un cursor implícito se ha utilizado por última vez. Estos atributos se deben utilizar en las sentencias PL/SQL, pero no en las sentencias SQL. Se pueden utilizar los atributos SQL%ROWCOUNT, SQL%FOUND, and SQL%NOTFOUND en la sección ejecutable de un bloque después que se ha ejecutado las sentencia DML. PL/SQL no devuelve un error si una sentencia DML no afecta a las filas de la tabla. Sin embargo, si una sentencia SELECT no recupera ninguna fila, PL/SQL devuelve una excepción. Los atributos tienen el prefijo SQL. Estos atributos del cursor se utilizan con los cursores implícitos que son creados automáticamente por PL/SQL y para los cuales no se conoce los nombres. Por lo tanto, se utiliza SQL en lugar del nombre del cursor. El atributo SQL%NOTFOUND es contrario a SQL%FOUND. Este atributo puede ser utilizado como la condición de salida en un loop. Es útil en UPDATE y DELETE cuando no se modifica ninguna fila porque en estos casos no se retornan excepciones.

CREATE TABLE empleados
AS SELECT * FROM employees;

DECLARE
  v_filas_elim    VARCHAR2(30);
  v_empno         empleados.employee_id%TYPE := 176;
BEGIN
    DELETE FROM  empleados 
    WHERE employee_id = v_empno;
    v_filas_elim := (SQL%ROWCOUNT || ' filas eliminadas.');
    DBMS_OUTPUT.PUT_LINE (v_filas_elim);
END;

Atributos de Cursor SQL para Cursores Implícitos
En el ejemplo, se eliminan las filas de la tabla EMPLEADOS que tienen el ID de empleado 176. Posteriormente se visualiza en total de filas eliminas. Par esto se usa el atributo %ROWCOUNT que retorna el total de filas afectadas por la última sentencia SQL ejecutada.

BEGIN
    UPDATE employees
    SET salary = salary + (salary * NVL(commission_pct,0))
    WHERE salary < 1000;
    IF SQL%NOTFOUND THEN
       DBMS_OUTPUT.PUT_LINE('No se actualizaron filas');
    ELSE
       DBMS_OUTPUT.PUT_LINE ('Se actualizaron ' || SQL%ROWCOUNT || ' filas');
    END IF;
END;

Atributos de Cursor SQL para Cursores Implícitos En el ejemplo, se aumenta el salario actual sumándole el valor de la comisión a los empleados con salario menor a 1000. Posteriormente se pregunta si la sentencia no afectó ninguna fila (con el atributo SQL%NOTFOUND) se mostrará el mensaje No se actualizaron filas , de lo contrario se visualizará el total de filas que fueron actualizadas (con el atributo SQL%ROWCOUNT).

lunes, 18 de mayo de 2015

Escribiendo Sentencias Ejecutables PL/SQL

Unidades Léxicas en un Bloque PL/SQL

Las unidades léxicas son los componentes básicos de cualquier bloque PL/SQL. Incluyen letras, números, tabulaciones, espacios, saltos de línea y símbolos.
Se pueden clasificar como:

  • Identificadores: Son empleados para nombrar objetos de programas en PL/SQL. Son utilizados para nombrar unidades dentro de un programa PL/SQL que incluyen: Constantes, Cursores, Variables, Subprogramas, Excepciones y Paquetes.
  • Delimitadores: es un símbolo simple o compuesto que tiene una función especial en PL/SQL. Estos pueden ser: Operadores Aritméticos, Operadores Lógicos y Operadores Relacionales.
  • Literales: valor asignado a una variable en forma explícita o textual.
  • Comentarios: texto de aclaración o documentación que se incluye en el bloque.

Unidades Léxicas en un Bloque PL/SQL

Símbolos simples

+ Operador de suma
- Operador de resta o negación
* Operador de multiplicación
/ Operador de división
= Operador de igualdad
@ Indicador de acceso remoto
; Término de sentencia
' Delimitador de caracteres
% Indicador de atributo
(y) Expresión/delimitador de lista
: Indicador de variable host
, Separador de item
' ' Delimitador de un identificador entre comillas

Símbolos Compuestos

:= Operador de asignación
=> Operador de asociación
|| Operador de concatenación
/* Delimitador de inicio de comentario de varias líneas
*/ Delimitador de fin de comentarios de varias líneas
.. Operador de rango
<> Operador de distinto
!= Operador de distinto
>= Operador de mayor igual
<= Operador de menor igual -- Comentario de una línea

Un literal es cualquier valor que se asigna a una variable en forma textual. Es cualquier carácter, número, booleano o valor de fecha que se asigna en forma explícita. Los literales se clasifican en:

  • Los literales de caracteres: Todos los literales de tipo string tienen el tipo de datos CHAR o VARCHAR2 y son, por lo tanto, llamados literales de caracteres (por ejemplo, Juan, y 12C). Los literales de caracteres y fecha deben ir entre comillas simples.
  • Los literales numéricos: Un literal numérico representa un valor entero o real (por ejemplo, 428 y 1.276).
  • Literales booleanos: valores que se asignan a las variables booleanas son literales booleanos. TRUE, FALSE y NULL son literales booleanos.
DECLARE
  v_comm  	NUMBER(3) :=50;
  v_fecha	DATE; 
  v_flag      BOOLEAN;
  v_nombre VARCHAR2(30) := 'MARÍA';
BEGIN
   v_fecha := '01/05/2020';
   IF v_comm = 0 THEN
       v_flag := TRUE;
  END IF;
END;

Los comentarios es una buena práctica de programación para explicar el código o parte del código de un bloque está tratando de hacer. Cuando se incluye un comentario en un bloque PL/SQL, el compilador no puede interpretar estas instrucciones. Los símbolos para definir un comentario indican al compilador que estas instrucciones no necesitan ser compilados. Por lo tanto, cualquier instrucción que se comenta no es interpretada por el compilador.

  • Un comentario de línea simple es precedido con 2 guiones (--).
  • Comentarios de múltiples líneas se coloca entre los símbolos /* y */.
DECLARE
   v_sal_anual   NUMBER (9,2);
BEGIN    -- Comienzo de la sección ejecutable /* La siguiente sentencia SQL obtiene desde tabla EMPLOYEES el salario anual del empleado con identificación igual a 100 */
     SELECT  SUM(salary * 12)
          INTO v_sal_anual
        FROM employees;
     DBMS_OUTPUT.PUT_LINE('Salario anual : ' ||  
     TO_CHAR(v_sal_anual, '$999,999,999'));
END;  -- Este es el fin del bloque

Funciones SQL en PL/SQL

Las funciones disponibles en sentencias procedimentales son aquellas funciones SQL que están disponibles para ser utilizadas en sentencias PL/SQL y en sentencias SQL que forman parte del bloque PL/SQL:

  • Funciones numéricas de una fila (single_row number): ROUND, TRUNC, MOD.
  • Funciones de carateres de una fila (single_row character): LOWER, UPPER, INITCAP, CONCAT, SUBSTR, etc
  • Conversiones de tipo de dato: TO_CHAR, TO_DATE etc
  • Funciones de fecha: SYSDATE, MONTH_BETWEEN, ADD_MONTHS, NEXT_DAY, etc
  • Funciones Timestamp.
  • GREATEST y LEAST
  • Funciones generales: COALESCE, NVL, NULLIF, etc.

Las funciones No Disponibles en sentencias PL/SQL son funciones SQL que sólo pueden ser utilizadas en sentencias SQL que forman parte del bloque PL/SQL y no en sentencias propias de PL/SQL:

  • DECODE.
  • Funciones de grupo: AVG, MIN, MAX, COUNT, etc.
DECLARE
  v_apell	       employees.last_name%TYPE;
  v_nombre       employees.first_name%TYPE;
  v_comision     employees.commission_pct%TYPE;
BEGIN
     SELECT last_name, UPPER(first_name), NVL(commission_pct,0)
          INTO v_apell, v_nombre, v_comision
        FROM employees
      WHERE employee_id = 110;
     DBMS_OUTPUT.PUT_LINE('Apellido empleado 110: ' || v_apell);
     DBMS_OUTPUT.PUT_LINE('Largo apellido empleado 110: ' || LENGTH(v_apell));  
     DBMS_OUTPUT.PUT_LINE('Nombre empleado 110 en mayúscula: ' || v_nombre);
     DBMS_OUTPUT.PUT_LINE('Porcentaje_comisión empleado 110: ' || v_comision);  
END;

En el bloque PL/SQL del ejemplo, se muestra el apellido en mayúscula, el largo del primer nombre y el porcentaje de comisión (si es nulo se mostrará el valor cero con la función NVL) del empleado 110. Las funciones LENGTH, UPPER y NVL pueden ser usadas en sentencias SQL y también en sentencias PL/SQL.

DECLARE
  v_sal_prom      PLS_INTEGER := 0;
  v_sal_mayor    NUMBER(8) := 0;
BEGIN
    SELECT ROUND(AVG(salary)), MAX(salary)
       INTO v_sal_prom, v_sal_mayor
     FROM employees;
    DBMS_OUTPUT.PUT_LINE('Salario promedio es: ' || TO_CHAR(v_sal_prom, '$999,999'));
    DBMS_OUTPUT.PUT_LINE('Salario mayor es: ' || TO_CHAR(v_sal_mayor, '$999,999'));  
END;

En el bloque del ejemplo, se obtiene el salario promedio (redondeado) y el salario máximo entre todos los empleados. Las funciones AVG y MAX sólo se puede utilizar en la sentencia SQL del bloque PL/SQL. Las funciones ROUND y TO_CHAR se pueden utilizar en sentencias SQL dentro del bloque y también en sentencias PL/SQL.

DECLARE
  v_apell        employees.last_name%TYPE;
  v_largo_apell NUMBER(2);
  v_nombre       employees.first_name%TYPE;
  v_comision     employees.commission_pct%TYPE;
BEGIN
  SELECT last_name, LENGTH(last_name), first_name, commission_pct
       INTO v_apell, v_largo_apell, v_nombre, v_comision
     FROM employees
   WHERE employee_id = 111;
  DBMS_OUTPUT.PUT_LINE('Apellido empleado 111: ' || v_apell);
  DBMS_OUTPUT.PUT_LINE('Largo apellido empleado 111: ' || v_largo_apell);  
  DBMS_OUTPUT.PUT_LINE('Nombre empleado 111 en mayúscula: ' || UPPER(v_nombre));
  DBMS_OUTPUT.PUT_LINE('Porcentaje_comisión empleado 111: ' || NVL(v_comision,0));    
END;

En este bloque PL/SQL, la función LENGTH ahora es usada en la sentencia SQL del bloque y las funciones UPPER y NVL son usadas en las sentencias PL/SQL.


Usar Secuencias en Expresiones PL/SQL

-- antes de 11g

DECLARE
  v_nuevo_id   NUMBER(6};
BEGIN
  SELECT nombre_secuencia.NEXTVAL 
      INTO v_nuevo_id 
    FROM dual;
END;

-- a contar de 11g

DECLARE
  v_nuevo_id   NUMBER(6);
BEGIN
  v_nuevo_id := nombre_secuencia.NEXTVAL;
END;

En Oracle Database 11g, se puede utilizar las pseudo-columnas NEXTVAL y CURRVAL en cualquier contexto PL/SQL donde exista una expresión de tipo de datos NUMBER. Aunque el viejo estilo de la utilización de una instrucción SELECT para consultar una secuencia sigue siendo válido, se recomienda que no usarlo.
Antes de Oracle Database 11g, era obligación a escribir una sentencia SQL con el fin de utilizar un valor de objeto de secuencia en un subprograma PL/SQL. Por lo general, era una instrucción SELECT para hacer referencia a los pseudo-columnas de NEXTVAL y CURRVAL para obtener un número de secuencia (Ejemplo)
En Oracle Database 11g, la limitación que obligaba a escribir una declaración de SQL para recuperar un valor de secuencia ya no existe (Ejemplo).


Conversión de Tipo de Datos

Cada constante o variable posee un tipo de dato el cual especifica su forma de almacenamiento, restricciones y rango de valores válidos. Se convierte un
valor desde un tipo a otro para tener tipos de datos comparables.

En PL/SQL existen dos tipos de conversiones:

Implícita: convierte tipos de datos automáticamente si son variados en una sentencia. Las conversiones implícitas pueden ocurrir entre caracteres y números ó caracteres y fechas:

  • De VARCHAR2 o CHAR a NUMBER.
  • De VARCHAR2 o CHAR a DATE.
  • De NUMBER a VARCHAR2.
  • DATE a VARCHAR2.

Explícita: es aquella que convierte un valor de un tipo a otro utilizando funciones predefinida:

  • TO_CHAR
  • TO_DATE
  • TO_NUMBER
  • TO_TIMESTAMP
DECLARE
v_fecha_reunion    DATE:= '09-Mar-2009';
v_fecha_contrato  DATE:= TO_DATE('Febrero 02,2000','Month DD, YYYY');
v_fecha_actual      VARCHAR2(20);
v_salario                NUMBER(6):=6000; 
v_bono                   VARCHAR2(5):='1000'; 
v_total_salario       v_salario%TYPE; 
BEGIN 
   SELECT TO_CHAR(sysdate,'Month DD, YYYY')
         INTO v_fecha_actual 
       FROM dual;
v_total_salario := v_salario + v_bono;
END;

En el bloque del ejemplo, valor asignado a la variable v_fecha_de_reunion es un string que tiene un formato de fecha válido, por lo tanto Oracle efectúa una conversión implícita. La variable v_fecha_contrato es de tipo de dato DATE, por lo tanto se usa la función TO_DATE para convertir explícitamente la fecha dada en un formato particular. La variable v_bono es de tipo VARCHAR2, por lo tanto cuando se efectúa el cálculo en la variable v_total_salario PL/SQL primero convierte v_bono a NUMBER (conversión implícita) y luego se realiza la sumatoria (el resultado de la operación será entonces de tipo NUMBER). En la sentencia Select se utiliza la función TO_CHAR para convertir en forma explícita la fecha actual en un formato tipo caracter.


Bloques PL/SQL Anidados

Una de las ventajas que PL/SQL tiene sobre SQL es la posibilidad de anidar sentencias. Si la sección ejecutable (BEGIN …. END) tiene código de muchas
funcionalidades de lógica relacionadas, se puede separar la sección ejecutable en bloques más pequeños. La sección de excepción también puede contener
bloques anidados.

DECLARE
v_variable_externa VARCHAR2(20):='VARIABLE GLOBAL';
BEGIN
     DECLARE
      v_variable_interna VARCHAR2(20):='VARIABLE LOCAL';
      BEGIN
              DBMS_OUTPUT.PUT_LINE(v_variable_interna);
              DBMS_OUTPUT.PUT_LINE(v_variable_externa);
      END;
DBMS_OUTPUT.PUT_LINE(v_variable_externa); 
END;

En el ejemplo, existen dos bloques PL/SQL. En el principal se declara la variable v_variable_externa que puede ser usada por ambos bloques. En el bloque interno se declara la variable v_variable_interna que solo puede ser usada en el bloque interno.


Ámbito y Visibilidad de la Variable

En PL/SQL las referencias a una variable son resueltas de acuerdo a su alcance y visibilidad dentro de un programa. El ámbito de una variable es aquella
parte del programa PL/SQL en la cual la variable es declarada y es accesible. La visibilidad de la variable es la parte del programa donde la
variable puede ser accesada sin utilizar un identificador o etiqueta (qualified).
Las variables declaradas en un bloque PL/SQL se consideran locales a ese bloque y globales para todos sus sub-bloques. Si una variable global es
re-declarada en un sub-bloques ambas pertenecen al mismo ámbito, sin embargo en el sub-bloque tan sólo la variable local es visible porque se debe utilizar
un qualified para referenciar a la global.

DECLARE
 nombre_padre       VARCHAR2(20):='Patricio';
 fecha_de_cumple  DATE:='20-Abr-1972';
BEGIN
   DECLARE
   nombre_hijo           VARCHAR2(20):='Miguel';
   fecha_de_cumple   DATE:='13-May-1992';
   BEGIN 
        DBMS_OUTPUT.PUT_LINE('Nombre del hijo: ' || ' ' || nombre_hijo);
        DBMS_OUTPUT.PUT_LINE('Dia de Cumpleaño: '||fecha_de_cumple);
        DBMS_OUTPUT.PUT_LINE(' Nombre del padre: ' || nombre_padre);
   END;
DBMS_OUTPUT.PUT_LINE('Dia de Cumpleaño: '||fecha_de_cumple); 
END;

En el ejemplo, en ambos bloques se declara la variable fecha_de_cumple. Por lo tanto, el bloque interno mostrará el valor asignado a su variable
13-May-1992 y el bloque externo mostrará el valor asignado a su variable 20-Abr-1972.

<>
DECLARE
 nombre_padre     VARCHAR2(20):='Patricio';
 fecha_de_cumple DATE:='20-Abr-1972';
BEGIN
   DECLARE
   nombre_hijo     VARCHAR2(20):='Miguel';
   fecha_de_cumple DATE:='13-May-1992';
   BEGIN 
      DBMS_OUTPUT.PUT_LINE('Nombre del padre:' || ' ' || nombre_padre);
      DBMS_OUTPUT.PUT_LINE('Dia de Cumpleaño: '|| padre.fecha_de_cumple);
      DBMS_OUTPUT.PUT_LINE('Nombre del hijo :' || ' ' || nombre_hijo);
      DBMS_OUTPUT.PUT_LINE('Dia de Cumpleaño: '|| fecha_de_cumple);
   END;
END;

Un calificador es un nombre dado a un bloque. Se puede utilizar un calificador para acceder a las variables que tienen un ámbito, pero no son visibles.
Basados en el mismo ejemplo anterior, si en el bloque interno se desea utilizar la variable fecha_de_cumple del bloque principal se debe utilizar una
etiqueta que identifique al bloque, que en este caso es padre, por lo tanto ahora en el bloque interno al referenciar padre.fecha_de_cumple mostrará el
valor de la variable del bloque principal y al referencia sólo fecha_de_cumple mostrará el valor de la variable del bloque interno.


Operadores PL/SQL

- Lógicos
- Aritméticos
- Comparaciones
- Concatenación
Paréntesis para controlar el orden de las operaciones

- Operador exponencial (**)

Las operaciones dentro de una expresión son realizadas en un orden particular dependiendo de su precedencia (prioridad) el que puede ser cambiado
utilizando paréntesis. El orden predeterminado de las operaciones de alta a baja prioridad es la siguiente:

  • ** : Exponenciación
  • + , - : Identificación, negación
  • *, / : Multiplicación, división
  • + , -, || : suma, resta, cocatenación
  • =, <, >, <=, >=, <>, !=, IS NULL, LIKE, BETWEEN, IN : Comparación
  • NOT : Negación
  • AND : Conjunción
  • OR : inclusión
DECLARE
v_loop_cuenta  NUMBER(3):=0;
v_buen_sal     BOOLEAN;
v_mensaje      VARCHAR2(40);
v_salario      NUMBER(8):=8000;
BEGIN
  v_loop_cuenta  :=  v_loop_cuenta + 1;
  v_buen_sal := v_salario BETWEEN 50000 AND 150000;
  v_mensaje := 'El nuevo salario es: ' || v_salario * 1.15;
END;

En el ejemplo, se incrementa el valor actual de la variable v_loop_cuenta en 1, se setea el valor de un Flag Boolean a la variable v_buen_salario
dependiendo del valor de la variable v_salario. Finalmente, la variable mensaje contendrá el mensaje El nuevo salario es: concatenado al resultado de
multiplicar el v_salario por 1.15.


Buenas Prácticas de Programación

Documentar el código con comentarios.

  • Escribir en mayúsculas:
  • Las sentencias SQL.
  • Las palabras reservadas de PL/SQL.
  • Los tipos de datos.

Escribir en minúsculas:

  • Los identificadores de variables.
  • Los parámetros.
  • Los nombres de tablas y columnas.

Indentar el código.

DECLARE
  v_id_emp                 employees.employee_id%TYPE;
  v_fecha_contrato    VARCHAR2(20);
  v_salario                  NUMBER(6):=6000; 
BEGIN 
    SELECT employee_id, TO_CHAR(hire_date,'Month DD, YYYY'), salary
    INTO v_id_emp, v_fecha_contrato, v_salario
    FROM employees
    WHERE  employee_id = 200;
    DBMS_OUTPUT.PUT_LINE('El empleado ' || v_id_emp || ' se contrató en ' ||
                    v_fecha_contrato || ' . Su salario mensual  es de $ ' || v_salario);
END;