sábado, 24 de septiembre de 2016

Cursores Explícitos.

Objetivos:
• Distinguir entre Cursores Implícitos y Explícitos.
• Discutir las razones para utilizar los cursores explícitos.
• Declarar y controlar cursores explícitos.
• Utilizar bucles simples y cursores FOR LOOP para obtener datos.
• Declarar y utilizar cursores con parámetros.
• Bloquear filas con la cláusula FOR UPDATE.
• Hacer referencia a la fila actual con la cláusula WHERE CURRENT OF.


NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
____________________________________________________________________________________
Los Cursores.
El servidor Oracle utiliza áreas de trabajo (llamadas zonas privadas SQL) al ejecutar sentencias SQL para almacenar y procesar información. Puede utilizar los cursores explícitos para nombrar una zona privada SQL y acceder a su información almacenada.

El servidor Oracle abre implícitamente un cursor para procesar cada instrucción SQL que no este asociada con un cursor declarado explícitamente.

Tipo

Descripción

Implícito

Son declarados por PL/SQL de forma implícita para cada DML o sentencia SELECT.
Explícito
Son ideales para consultas que devuelven varias filas; son declarados y gestionados por el programador, y se manipulan a través de sentencias específicas en sentencias ejecutables del bloque.
Operaciones de los Cursores Explícitos.
Se declaran cursores explícitos en PL/SQL cuando se tiene una instrucción SELECT que devuelve varias filas, ya que con el cursor es posible procesar cada fila devuelta.

Se conoce como el active set(conjunto activo) al conjunto de filas devueltas por una consulta de múltiples filas. Su tamaño es el número de filas que coinciden con su criterio de búsqueda.

Funciones de los Cursores Explícitos.
• Puede realizar el procesamiento fila por fila más allá de la primera fila devuelta por una consulta.
• Mantiene un registro de la fila que se está procesando actualmente.
• Permite al programador controlar manualmente los cursores explícitos en el bloque PL/SQL.

Manejando Cursores Explícitos.
Ahora que tiene una comprensión conceptual de los cursores, revise los pasos para utilizarlos.
1. En la parte declarativa de un bloque PL/SQL, declare el cursor, asignele un nombre y defina la estructura de su consulta.
2. Abra el cursor.
La sentencia OPEN ejecuta la consulta y vincula las variables que se hayan referenciado. Las filas identificadas por la consulta(active set) ahora están disponibles para el FETCH.
3. Obtener(FETCH) los datos del cursor.
En el diagrama de flujo anterior: Luego de cada búsqueda(FETCH), se prueba la existencia de filas en el cursor. Si no hay más filas para procesar, se debe cerrar el cursor.
4. Cierre el cursor.
La sentencia CLOSE libera el active set de filas. Ahora es posible volver a abrir el cursor para establecer un nuevo active set.

Detalles:
1. La sentencia OPEN ejecuta la consulta asociada con el cursor, identifica el active set, y posiciona el cursor en la primera fila.
2. La sentencia FETCH recupera la fila actual y avanza el cursor a la siguiente hasta que no haya más filas o hasta que cierta condición se cumpla.
3. La sentencia CLOSE libera el cursor.

Sintaxis:
CURSOR cursor_name IS
     select_statement;

En la Sintaxis:
• cursor_name: Es un identificador PL/SQL.
• select_statement: Es una sentencia SELECT sin la cláusula INTO.

En un cursor el active set está determinado por el SELECT al momento de su declaración. En PL/SQL es obligatorio tener una cláusula INTO al usar una sentencia SELECT. Sin embargo, tenga en cuenta que la sentencia SELECT en la declaración de cursor no puede tener dicha cláusula. Esto se debe a que sólo se está definiendo el cursor en la parte declarativa, no recuperando su filas.

Notas:
• No incluya la cláusula INTO en la declaración del cursor, ya que está se usa más adelante en la sentencia FETCH.
• Si desea que las filas sean procesadas en algún orden especifico, utilice la cláusula ORDER BY en la consulta.
• El cursor puede ser cualquier sentencia SELECT válida, incluyendo JOINs, UNIONs, subconsultas, y así sucesivamente.

Ejemplos:
DECLARE
    v_dept  NUMBER := 30;
    CURSOR c_emps IS
        SELECT  employee_id,
                last_name,
                salary,
                job_id
        FROM employees
        WHERE department_id = v_dept;
---
    CURSOR c_dept_sal IS
        SELECT  d.department_name,
               SUM(e.salary)
        FROM employees e, departments d
        WHERE e.department_id = d.department_id
        GROUP BY d.department_name;
/*El ejemplo muestra la declaración de dos cursores: v_dept y c_dept_salc_emps consulta algunas informaciones de los empleados del departamento que contenga la variable v_dept, que en este caso es 30;  c_dept_sal consulta la suma de salarios de los empleados de cada departamento.*/
---
Sentencia OPEN.
La sentencia OPEN ejecuta la consulta asociada con el cursor, identifica el active set, y posiciona el puntero en la primera fila del cursor. La sentencia OPEN se incluye en la sección ejecutable del bloque PL/SQL.

OPEN es una sentencia ejecutable que realiza las siguientes operaciones:
1. Dinámicamente asigna la memoria para un área de contexto.
2. Analiza la sentencia SELECT.
3. Vincula las variables de entrada (establece los valores de las variables de entrada mediante la obtención de sus direcciones de memoria).
4. Identifica el conjunto activo (el conjunto de filas que satisfacen los criterios de búsqueda). Las filas en el active set no son asignadas a variables cuando se ejecuta la sentencia OPEN. Más bien, la sentencia FETCH es la que hace ese trabajo.
5. Sitúa el puntero a la primera fila del active set.

Nota: Si una consulta no devuelve ninguna fila cuando se abre el cursor, PL/SQL no lanza una excepción. Puede averiguar el número de filas devueltas por un cursor explícito mediante el uso del atributo <cursor_name>%ROWCOUNT.

Sentencia FETCH.
La sentencia FETCH recupera las filas del cursor de una en una. Después de cada FETCH, el cursor avanza a la siguiente fila del active set. Puede utilizar el atributo %NOTFOUND para determinar si todo el active set se ha recuperado.

La sentencia FETCH realiza las siguientes operaciones:
1. Lee y asigna los datos de la fila actual a las variables de PL/SQL.
2. Avanza el puntero a la siguiente fila del active set.

El número de variables en la cláusula INTO de la sentencia FETCH debe ser igual al número de columnas en la sentencia SELECT del cursor; asegúrese de que los tipos de datos sean compatibles y que estén en el orden correcto. Alternativamente, también puede definir un récord para el cursor y referenciarlo en la cláusula INTO del FETCH.

Sentencia CLOSE.
La sentencia CLOSE desactiva el cursor, libera el área de contexto, y "in-define" el active set. Es recomendable siempre cerrar el cursor después de completar el proceso. Si es necesario, puede volver a abrir el cursor, pero tenga en cuenta que un cursor puede volver a abrir solamente si está cerrado. Si intenta obtener datos de un cursor cerrado, se produce la excepción: INVALID_CURSOR.

Nota: Aunque es posible terminar el bloque PL/SQL sin cerrar los cursores, es recomendable hacer hábito de cerrar cualquier cursor que se declara explícitamente,  ya que esto libera recursos. Hay un límite máximo en el número de cursores abiertos por sesión, lo cual está determinado por el parámetro OPEN_CURSORS en el archivo de parámetros de Base de Datos. (Por defecto OPEN_CURSORS = 50).

Ejemplos:
SET SERVEROUTPUT ON
DECLARE
     CURSOR c_emp_cursor IS 
          SELECT
                   employee_id, 
                   last_name
          FROM employees
          WHERE employee_id = 111;
--
     v_empno employees.employee_id%TYPE;
     v_lname employees.last_name%TYPE;
BEGIN
     OPEN c_emp_cursor;   --abre el cursor
     FETCH c_emp_cursor INTO v_empno, v_lname;   --asigna los valores a las variables.
     CLOSE c_emp_cursor;       --cierra el cursor.
--
     DBMS_OUTPUT.PUT_LINE( v_empno ||'  '||v_lname); 
END;
/*Este es un ejemplo simple de un cursor, en el cual se define un cursor que extrae el código y apellido de un empleado; notar como son asignados los datos extraídos a las variables previamente declaradas.*/
---
SET SERVEROUTPUT ON
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;   --abre el cursor
      LOOP
            FETCH c_emp_cursor INTO v_empno, v_lname;   --asigna los valores a las variables.
            EXIT WHEN c_emp_cursor%NOTFOUND;
--
            DBMS_OUTPUT.PUT_LINE( v_empno ||'  '||v_lname);  
      END LOOP;
      CLOSE c_emp_cursor;       --cierra el cursor.
END;
/*Este ejemplo muestra como usar un cursor que retorne mas de una fila de resultados; notar como se itera el cursor  con la ayuda de un LOOP simple.*/
---
Cursores y Records.
Ya ha visto que se puede definir records con la estructura de columnas de una tabla. También puede definir un records basado en la lista de columnas en el SELECT de un cursor explícito. Esto es conveniente para procesar las filas del active set, ya que solo tiene que especificar el nombre del record en la cláusula INTO de la sentencia FETCH, en lugar de un numero x de variable. Esto también asegura que los valores de las filas se cargaran directamente en los campos correspondientes del record.

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
      CURSOR c_emp_cursor IS 
           SELECT
                    employee_id, 
                    last_name,
                    salary,
                    department_id,
                    manager_id
           FROM employees
           WHERE department_id = 30;
--
      v_emp_rec     c_emp_cursor%ROWTYPE;
BEGIN
      OPEN c_emp_cursor;   --abre el cursor
      LOOP
            FETCH c_emp_cursor INTO v_emp_rec;   --asigna los valores a las variables.
            EXIT WHEN c_emp_cursor%NOTFOUND;
--
            DBMS_OUTPUT.PUT_LINE('Nombre: '||v_emp_rec.last_name||', Salario: '||v_emp_rec.salary||
                                     ', Departamento: '||v_emp_rec.department_id);  
      END LOOP;
      CLOSE c_emp_cursor;       --cierra el cursor.
END;
/*Modificamos el último ejemplo agregandole mas columnas; notar como el record basado en el cursor simplifica el código, evitando tener que declarar tantas variables como columnas en el SELECT.*/
____________________________________________________________________________________
Cursores FOR LOOP.
Un Cursor FOR LOOP procesa las filas de un cursor explícito. Se puede considerar como un atajo, esto porque se abre el cursor, una fila es extraída(FETCH) por cada iteración del LOOP, el LOOP termina cuando se procesa la última fila, y el cursor se cierra automáticamente. El LOOP en sí se termina automáticamente al final de la iteración, donde la última fila se recupera.

Sintaxis:
FOR record_name IN cursor_name LOOP
    statement1;
    statement2;
    . . .
END LOOP;

En la sintaxis:
• record_name:      Es el nombre del record declarado implícitamente.
• cursor_name:      Es el nombre del cursor declarado previamente.

Directrices:
• No declare el record que usara en el LOOP; ya que se declara de forma implícita.
• Puede usar los atributos de cursor durante el LOOP si es necesario.
• Suministrar los parámetros del cursor, si se requiere, entre paréntesis a continuación del nombre del cursor en la sentencia FOR.

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
      CURSOR c_emp_cursor IS 
           SELECT
                    employee_id, 
                    last_name,
                    salary,
                    department_id,
                    manager_id
           FROM employees
           WHERE department_id = 30;
--
      v_emp_rec     c_emp_cursor%ROWTYPE;
BEGIN
      FOR i IN c_emp_cursor LOOP
--
            DBMS_OUTPUT.PUT_LINE('Nombre: '||i.last_name||', Salario: '||i.salary||
                                     ', Departamento: '||i.department_id);  
      END LOOP;
END;
/*El mismo ejemplo ahora usando un FOR LOOP; Notar como se simplifica el código aun mas ya que no hay necesidad de abrir, hacer FETCH ni cerrar el cursor.*/
____________________________________________________________________________________
Atributos de los Cursores Explícitos.
Atributo
Tipo
Descripción
%ISOPEN
Boolean
Retorna TRUE si el cursor está abierto.
%NOTFOUND
Boolean
Retorna TRUE si el último FETCH no trajo una fila.
%FOUND
Boolean
Retorna TRUE si el último FETCH trajo una fila; opuesto a %NOTFOUND.
%ROWCOUNT
Number
Cantidad de filas que han sido extraídas(FETCH).
Al igual que con los cursores implícitos, hay cuatro atributos que proveen información referente al estado de un cursor. Una vez adjunto al nombre del cursor, estos atributos devuelven información útil acerca de la ejecución del cursor.

Nota: No puede hacer referencia a los atributos de un cursor directamente en una sentencia SQL.

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
      CURSOR c_emp_details IS 
           SELECT
                    e.first_name||' '||e.last_name AS nombre, 
                    e.salary AS salario,
                    d.department_name AS departamento,
                    j.job_title AS empleo,
                    m.first_name||' '||m.last_name AS supervisor,
                    l.city AS ciudad
           FROM employees e, employees m, departments d, locations l, jobs j
           WHERE NVL(e.manager_id, 0) = m.employee_id
           AND e.job_id = j.job_id
           AND e.department_id = d.department_id
           AND d.location_id = l.location_id;
--
      v_emp_details_rec     c_emp_details%ROWTYPE;
BEGIN
      IF NOT c_emp_details%ISOPEN THEN
          OPEN c_emp_details;
          DBMS_OUTPUT.PUT_LINE('Cursor Abierto');
      END IF;
--
      LOOP
            FETCH c_emp_details INTO v_emp_details_rec;
            EXIT WHEN c_emp_details%NOTFOUND;
--
            DBMS_OUTPUT.PUT_LINE('FETCH #: '||c_emp_details%ROWCOUNT);
--
            DBMS_OUTPUT.PUT_LINE(
                                     'Nombre: '||v_emp_details_rec.nombre||CHR(10)||
                                     'Salario: '||v_emp_details_rec.salario||CHR(10)||
                                     'Departamento: '||v_emp_details_rec.departamento||CHR(10)||
                                     'Empleo: '||v_emp_details_rec.empleo||CHR(10)||
                                     'Supervisor: '||v_emp_details_rec.supervisor||CHR(10)||
                                     'Ciudad: '||v_emp_details_rec.ciudad
                                    );
      END LOOP;
--
      IF c_emp_details%ISOPEN THEN
          CLOSE c_emp_details;
          DBMS_OUTPUT.PUT_LINE('Cursor Cerrado');
      END IF;
END;
/*En el ejemplo creamos un cursor que contiene informaciones de los empleados y un record basado en ese cursor; notar el uso de los atributos del cursor en la sección de ejecución del bloque.*/
---
Cursores FOR LOOP con Subconsultas.
La diferencia entre un cursor FOR LOOP con una subconsulta y un cursor FOR LOOP se encuentra en la declaración de cursor. Si desea usar un cursor FOR LOOP con una subconsulta, no es necesario declarar el cursor en la parte declarativa. Usted tiene que proporcionar la sentencia SELECT que determina el active set en el mismo LOOP.

Nota: No se puede hacer referencia a los atributos del cursor FOR LOOP con una subconsulta, esto debido a que se desconoce el nombre del mismo.

Ejemplo:
SET SERVEROUTPUT ON
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;
/*En el ejemplo un cursor FOR LOOP con una subconsulta; notar como este bloque no necesita la seccion declarativa.*/
---
Cursores Con Parámetros.
Puede pasar parámetros a un cursor. Esto significa que puede abrir y cerrar un cursor varias veces y retornar un active set diferente en cada ocasión. Para cada ejecución, el cursor es cerrado y abierto de nuevo con un conjunto de valores diferentes.

Los tipos de datos de los parámetros son los mismos que para las variables escalares, con la diferencia de que al parámetro no se le especifica la precisión(tamaño). Los nombres de los parámetros son para referencia en la consulta del cursor.

Sintaxis:
CURSOR cursor_name[(parameter_name datatype, ...)]
IS
    select_statement;
--
OPEN    cursor_name(parameter_value,.....);
--
FOR record_name IN cursor_name(parameter_value,.....) LOOP

En la sintaxis:
cursor_name
Es un identificador del cursor.
parameter_name
Es el nombre del parámetro.
datatype
Es el tipo de dato del parámetro.
select_statement
Es la sentencia SELECT.

La notación de parámetro no ofrece mejor funcionalidad; simplemente le permite especificar valores de entrada  de forma fácil y clara. Esto es particularmente útil cuando se hace referencia repetidamente del mismo cursor.

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
    CURSOR c_emp_details(p_emp_id NUMBER) IS
        SELECT
                e.first_name||' '||e.last_name AS nombre,
                e.salary AS salario,
                d.department_name AS departamento,
                l.city AS ciudad
        FROM employees e, departments d, locations l
        WHERE e.employee_id = p_emp_id
        AND e.department_id = d.department_id
        AND d.location_id = l.location_id;
--
    CURSOR c_job_dept_details(p_dept_id NUMBER, p_job_id VARCHAR2) IS
        SELECT
                d.department_name AS departamento,
                j.job_title AS empleo,
                COUNT(e.employee_id) AS emp_count,
                SUM(e.salary) AS total_sal
        FROM employees e, departments d, jobs j
        WHERE d.department_id = p_dept_id
        AND j.job_id = p_job_id
        AND e.department_id = d.department_id
        AND e.job_id = j.job_id 
        GROUP BY d.department_name, j.job_title;
--
    v_emp_details_rec   c_emp_details%ROWTYPE;
BEGIN
    OPEN c_emp_details(121);
    FETCH c_emp_details INTO v_emp_details_rec;
    CLOSE c_emp_details;
--
    DBMS_OUTPUT.PUT_LINE('Primer Cursor');
    DBMS_OUTPUT.PUT_LINE('Empleado: '||v_emp_details_rec.nombre||CHR(10)||
                             'Salario: '||v_emp_details_rec.salario||CHR(10)||
                             'Departamento: '||v_emp_details_rec.departamento||CHR(10)||
                             'Ciudad: '||v_emp_details_rec.ciudad);
--
    DBMS_OUTPUT.PUT_LINE('Segundo Cursor');
--
    FOR i IN c_job_dept_details(50,'ST_MAN') LOOP
--
        DBMS_OUTPUT.PUT_LINE('Departamento: '||i.departamento||CHR(10)||
                                 'Empleo: '||i.empleo||CHR(10)||
                                 'Cantidad de Empleados: '||i.emp_count||CHR(10)||
                                 'Salario Total: '||i.total_sal);
    END LOOP;
END;
/*El ejemplo muestra el uso de dos cursores con parámetro, el primero recibe el numero de empleado y retorna ciertas informaciones del mismo; el segundo recibe el código de departamento y el código de empleo y retorna la cantidad de empleados y la suma de salarios. Tenga en cuenta que estos cursores pueden ser usados repetidamente en el mismo bloque con parámetros distintos para así generar diferentes resultados.*/
____________________________________________________________________________________
Referenciar y Bloquear la Actual Fila del Cursor.

Cláusula FOR UPDATE.
Si hay varias sesiones en una Base de Datos, existe la posibilidad de que las filas de una tabla en particular se actualizan después de haber abierto un cursor. De modo que usted ve los datos actualizados sólo cuando vuelve a abrir el cursor. Por lo tanto, es mejor bloquear las filas antes de actualizarlas o eliminarlas. Es posible bloquear las filas con la cláusula FOR UPDATE en la consulta del cursor.

Sintaxis:
SELECT    ... 
FROM        ...
FOR UPDATE [OF column_reference][NOWAIT | WAIT n];

En la sintaxis:
column_reference
Es una columna de la tabla en la que se realiza la consulta (también se puede utilizar una lista de columnas).
NOWAIT
Devuelve un error de Oracle si las filas están bloqueadas por otra sesión.
La cláusula FOR UPDATE es la última en una sentencia SELECT, incluso después de ORDER BY (si existe). Cuando se desea consultar varias tablas, puede utilizar la cláusula FOR UPDATE para confinar el bloqueo de filas en tablas particulares. FOR UPDATE OF col_name(s) bloquea filas sólo en las tablas que contienen col_name(s).

La sentencia SELECT ... FOR UPDATE identifica las filas que se van a actualizar o eliminar y, a continuación, bloquea cada fila del active set. Esto es útil cuando se desea realizar una actualización en los valores existentes en una fila. En ese caso, se debe asegurar de que la fila no sea cambiada por otra sesión antes de la actualización.

La palabra clave NOWAIT(opcional) le indica al Servidor Oracle no esperar si las filas seleccionadas han sido bloqueadas por otro usuario. En caso de que hayan sido bloqueadas, el control vuelve inmediatamente a tu programa de modo que pueda hacer otro trabajo antes de volver a intentar adquirir el bloqueo. Si se omite la palabra clave NOWAIT, el servidor Oracle espera hasta que las filas estén disponibles.

Ejemplo:
DECLARE
    CURSOR c_emp_cursor IS 
        SELECT employee_id, last_name, salary
        FROM  employees
        WHERE department_id = 80
        FOR UPDATE OF salary NOWAIT

Si el servidor de Oracle no puede adquirir el bloqueo de las filas que necesita en una operación SELECT FOR UPDATE, se espera indefinidamente. Utilizar NOWAIT para manejar este tipo de situaciones. Si las filas están bloqueados por otra sesión y se ha especificado NOWAIT, al abrir el cursor se produce un error. Se puede utilizar WAIT en lugar de NOWAIT, y si usa WAIT puede especificar el número de segundos de espera. Si luego de haber transcurrido los n segundos las filas continúan bloqueadas, se devuelve un error.

No es obligatorio especificar una columna en la cláusula FOR UPDATE OF, pero se recomienda para una mejor legibilidad y mantenimiento.

Cláusula WHERE CURRENT OF.
• Usar cursores para actualizar o eliminar la fila actual.
• Incluir la cláusula FOR UPDATE en la consulta de cursor para primero bloquear la fila.
• Utilice la cláusula WHERE CURRENT OF para hacer referencia a la fila actual de un cursor explícito.

La cláusula WHERE CURRENT OF se utiliza en conjunto con la cláusula FOR UPDATE para referirse a la fila actual de un cursor explícito. La cláusula WHERE CURRENT OF se utiliza en las sentencias UPDATE o DELETE; primero es necesario especificar la cláusula FOR UPDATE en la declaración del cursor. Se puede utilizar la combinación de actualización y supresión de un fila actual de la tabla de Base de Datos correspondiente. Esto le permite aplicar las actualizaciones y eliminar la fila que en cuestión, sin la necesidad de hacer referencia explícita del ID de la misma.

Sintaxis:
UPDATE table_name
SET column_name = ...
WHERE CURRENT OF cursor_name;
--
DELETE FROM table_name
WHERE CURRENT OF cursor_name;

Ejemplo:
DECLARE
    CURSOR c_emp IS 
        SELECT employee_id, last_name, salary
        FROM  employees
        WHERE employee_id = 100
        FOR UPDATE OF salary    WAIT 180
    v_emp_rec c_emp%ROWTYPE;
BEGIN
    OPEN c_emp;
    FETCH c_emp INTO v_emp_rec;
--
    UPDATE employees
    SET salary = 30000
    WHERE CURRENT OF c_emp;
    COMMIT;
--
    CLOSE c_emp;
END;
/*Este SCRIPT tratara de bloquear para luego actualizar el salario del registro con el employee_id = 100 de la tabla employees, en caso de ya estar bloqueado por otro usuario, el bloque esperaría máximo 3 minutos para que se libere dicho registro, de no ser bloqueado en este tiempo un error se produciría.*/
_____________________________________________________________________________________
Fuente: Oracle Database: PL/SQL Fundamentals

sábado, 17 de septiembre de 2016

Tipos de Datos Compuestos.

Objetivos:
• Describir los records y collections de PL/SQL.
• Crear records definidos por el usuario.
• Crear un record con el atributo %ROWTYPE.
• Crear matrices asociativas(associative arrays):
--INDEX BY table.
--INDEX BY table of records.
NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
____________________________________________________________________________________
Tipos de Datos Compuestos.
Las variables escalares pueden contener sólo un valor, mientras que las variables de tipo de dato compuesto pueden contener varios valores de tipo de dato escalar o el tipo de dato compuesto. Los dos tipos de tipos de datos compuestos disponibles en PL/SQL son:

• PL/SQL Records: Los Records se utilizan para tratar como una unidad lógica tipos de datos distintos que guardan relación. Un Records de PL/SQL puede tener variables de diferentes tipos. Por ejemplo, puede definir un Record para contener detalles de empleados. Esto implica el almacenamiento de un número de empleado como NUMBER, un nombre y un apellido como VARCHAR2, y así sucesivamente. Mediante la creación de un Record para almacenar detalles de empleados, se crea una unidad lógica colectiva. Esto hace más fácil el acceso y la manipulación de los datos.

• PL/SQL Collections: Las Colecciones se utilizan para tratar datos como una sola unidad. Las colecciones son de tres tipos:
--Associative array.
--Nested table.
--VARRAY.

¿Por qué usar de datos compuestos?
Usted tiene todos los datos relacionados como una sola unidad. Puede acceder y modificar fácilmente los datos.  Si están compuesto, los datos resultan más fáciles de manejar, relacionar, y el transportar. Una analogía es tener solo una bolsa para todos los componentes de su computadora portátil en lugar de una bolsa separada para cada componente.

Si los Records y Colecciones  de PL/SQL son ambos tipos de datos compuestos, ¿Cómo elegir cuál usar?
• Utilice Records cuando se desea almacenar valores de diferentes tipos de datos que están relacionados lógicamente. Por ejemplo, puede crear un Record para contener detalles de empleados y indicar que todos los valores almacenados están relacionados, ya que proporcionan información sobre un empleado en particular.

• Utilice Colecciones cuando se desea almacenar valores del mismo tipo de dato. Tenga en cuenta que este tipo de dato también puede ser de tipo compuesto (por ejemplo, Records). Se puede definir una colección para almacenar el primer nombre de todos los empleados. Puede que haya guardado n nombres en la colección; Sin embargo, el nombre 1 no está relacionado con el nombre 2. La única relación posible es que son nombres de los empleados. Estas colecciones son similares a las matrices en lenguajes de programación como C, C++ y Java.
____________________________________________________________________________________
Records de PL/SQL.

Los Records de PL/SQL:
• Deben contener uno o más componentes (llamados campos) de cualquier tipo: escalarRecord o INDEX BY table.
• Son similares a las estructuras de la mayoría de lenguajes de tercera generación (incluyendo C y C ++).
• Son definidos por el usuario y pueden ser un subconjunto de una fila de una tabla.
• Trata una colección de campos como una unidad lógica.
• Son convenientes para extraer una fila de datos de una tabla para su procesamiento.

Un Record es un grupo de elementos de datos relacionados almacenados en campos, cada campo con su propio nombre y tipo de dato:
• Cada Record puede tener tantos campos como sea necesario.
• A los Records se les puede asignar valores iniciales y pueden definirse como NOT NULL.
• A los campos sin valores iniciales son inicializados con NULL.
• La palabra clave DEFAULT, así como := se pueden utilizar en la inicialización de campos.
• Se pueden definir tipos(types) de Records y declarar Records definidos por el usuario en la parte declarativa de cualquier bloque, subprograma o paquete.
• Se puede declarar y hacer referencia a los Records anidados. Un Record puede ser un componente de otro Record.

Sintaxis:
TYPE type_name IS RECORD
             (
                field_declaration[, 
                field_declaration]…
             );
--
field_declaration:
field_name {field_type | variable%TYPE 
           | table.column%TYPE | table%ROWTYPE}
           [[NOT NULL] {:= | DEFAULT} expr]
--
identifier  type_name;

En la sintaxis:
type_name
Es el nombre del tipo Record (Este identificador se utiliza para declarar las variables tipo Records).
field_name
Es el nombre de un campo dentro del Record.
field_type
Es el tipo de dato del campo (Representa cualquier tipo de dato PL/SQL excepto REF CURSOR. Puede utilizar los atributos %ROWTYPE y %TYPE).
expr
Es el valor inicial.
La restricción NOT NULL impide la asignación de valores nulos en los campos especificados. Asegúrese de inicializar los campos NOT NULL.

Ejemplo:
DECLARE
    TYPE client_typ_rec IS RECORD
        (
            codigo          NUMBER(6),
            nombre          VARCHAR2(35),
            direccion       VARCHAR2(50),
            telefono        VARCHAR2(10),
            fec_ingreso     DATE
        );
--
    v_cliente   client_typ_rec;
BEGIN
    v_cliente.codigo := 1;
    v_cliente.nombre := 'Julio Aponte';
    v_cliente.fec_ingreso := SYSDATE;
END;
/*En el ejemplo se crea un tipo recordclient_typ_rec, luego se crea una variable(v_cliente) de ese tipo record y posteriormente se le asigna valores a los campos de la variable en la sección ejecutable del bloque.*/
____________________________________________________________________________________
Atributo %ROWTYPE.
Ya vimos que %TYPE se utiliza para declarar una variable de tipo columna. Dicha variable tiene el mismo tipo de dato y tamaño que la columna de la tabla. El beneficio de %TYPE es que usted no tiene que cambiar la variable si se altera la columna. Además, si la variable es un número y se utiliza en los cálculos, usted no necesita preocuparse acerca de su precisión.

El atributo %ROWTYPE se utiliza para declarar un Record que contenga toda la fila de una tabla o vista. Los campos en el Record toman los nombres y tipos de datos de las columnas de la tabla o vista. El Record también puede almacenar toda una fila de datos obtenida de un cursor o variable de cursor.

Sintaxis:
DECLARE
   identifier     reference%ROWTYPE;

En la Sintaxis:
identifier
Es el nombre de la variable tipo record.
reference
Es el nombre de la tabla, vista, cursor, o variable de cursor en el cual se basa la variable record (Para que la referencia sea validad, la tabla o vista debe existir).
Ejemplo:
DECLARE
    v_rec_empleado      employees%ROWTYPE;
....
El Record v_rec_empleado tiene una estructura que consta de los siguientes campos, cada uno representando una columna en la tabla empleados.
(
   employee_id       NUMBER(6),
   first_name        VARCHAR2(20),
   last_name         VARCHAR2(20),
   email             VARCHAR2(20),
   phone_number      VARCHAR2(20),
   hire_date         DATE,
   salary            NUMBER(8,2),
   commission_pct    NUMBER(2,2),
   manager_id        NUMBER(6),
   department_id     NUMBER(4)
)
Asignando valores a los Records.
Puede asignar una lista de valores comunes a un registro utilizando la sentencias SELECT o FETCH. Asegúrese de que los nombres de columna aparezcan en el mismo orden que los campos en su Record. También puede asignar un Record a otro, si ambos tienen los mismos tipos de datos. Un Record de tipo employees%ROWTYPE y un tipo Record definido por el usuario con campos análogos de la tabla employees tendrán los mismos tipos de datos. Por lo tanto, si un Record definido por el usuario contiene campos similares a los de un Record %ROWTYPE, sera posible asignar ese Record definido por el usuario a el Record %ROWTYPE.

Las declaraciones de campos utilizados en la definición de un Record son como las declaraciones de variables. Cada campo tiene un nombre único y un tipo de datos específico. No hay tipos de datos predefinidos para los Records de PL/SQL, como en el caso de las variables escalares. Por lo tanto, se requiere crear el tipo Record en primer lugar, y luego declarar un identificador usando ese tipo.

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
    TYPE t_rec IS RECORD
      (
       v_sal              NUMBER(8),
       v_minsal           NUMBER(8) DEFAULT 1000,
       v_fecha_contrato   employees.hire_date%TYPE,
       v_rec1             employees%ROWTYPE
      );
---
    v_mirec t_rec;
BEGIN
    v_mirec.v_sal := v_mirec.v_minsal + 500;
    v_mirec.v_fecha_contrato := SYSDATE;
---
    SELECT * INTO v_mirec.v_rec1
    FROM employees
    WHERE employee_id = 100;
---
    DBMS_OUTPUT.PUT_LINE(v_mirec.v_rec1.last_name
                            ||' '||to_char(v_mirec.v_fecha_contrato)
                            ||' '|| to_char(v_mirec.v_sal));
END;
---
En el ejemplo, un Record de PL/SQL se crea utilizando los dos pasos necesarios:
1. Se define un tipo Record (t_rec).
2. Se declara un Record (v_mirec) del tipo t_rec.
Nota:
• El Record contiene cuatro campos: v_sal, v_minsalv_fecha_contrato, y v_rec1.
• v_rec1 se define mediante el atributo %ROWTYPE, que es similar al atributo %TYPE. Con %TYPE, un campo hereda el tipo de dato de una columna especificada. Con %ROWTYPE, el campo hereda los nombres y tipos de datos de todas las columnas de la tabla referenciada.
• Los campos de un Record de PL/SQL se pueden referenciar mediante la notación <Record>.<campo>, o con <Record>.<campo>.<columna> para los campos que se definen con el atributo %ROWTYPE.
• Puede añadir la restricción NOT NULL a cualquier declaración de campo para evitar la asignación de valores nulos a ese campo. Recuerde que los campos declarados como NOT NULL deben inicializarse.

Ventajas del Atributo %ROWTYPE.
• No es necesario conocer el número y tipos de datos de las columnas de Base de Datos y, de hecho, dichas columnas podrían cambiar en tiempo de ejecución sin afectar el Record.
• El atributo %ROWTYPE es útil cuando se desea recuperar una fila con:
--La instrucción SELECT *
--Sentencias INSERT y UPDATE a nivel de fila.

Utilice el atributo %ROWTYPE cuando no está seguro acerca de la estructura de la tabla de Base de Datos subyacente.

La principal ventaja de utilizar %ROWTYPE es que simplifica el mantenimiento. Usando %ROWTYPE asegura que los tipos de datos de las variables declaradas con este atributo cambian dinámicamente cuando se altera la tabla subyacente. Si una instrucción DDL cambia las columnas de una tabla, el programa PL/SQL se invalida. Cuando se vuelve a compilar el programa, se refleja automáticamente el nuevo formato de tabla.

El atributo %ROWTYPE es particularmente útil cuando se desea recuperar toda una fila de una tabla. En ausencia de este atributo, se verá forzado a declarar una variable para cada columna devuelta por la sentencia SELECT.

Ejemplos:
CREATE TABLE emps_retirados
   (
    codigo        NUMBER(4),
    nombre        VARCHAR2(10),
    empleo        VARCHAR2(9),
    manager       NUMBER(4),
    fec_entrada   DATE
    fec_salida    DATE,
    salario       NUMBER(7,2), 
    comision      NUMBER(7,2),
    departameto   NUMBER(2)
  );
/*Creamos esta tabla que nos servirá de ejemplo.*/
---
DECLARE
    v_codigo_emp    NUMBER := 124;
    v_emp_rec       emps_retirados%ROWTYPE;
BEGIN
    SELECT
            employee_id,
            last_name,
            job_id,
            manager_id,
            hire_date,
            SYSDATE,
            salary,
            commission_pct,
            department_id
    INTO v_emp_rec
    FROM employees
    WHERE employee_id = v_codigo_emp;
---
    INSERT INTO emps_retirados
    VALUES v_emp_rec;
---
    COMMIT;
END;
/
SELECT *
FROM emps_retirados;
/*Este SCRIPT recupera los datos del empleado 124 de la tabla employees y luego los inserta en la tabla emps_retirados; notar como se simplifica la sentencia INSERT con el uso del atributo %ROWTYPE.*/
---OUTPUT:
DECLARE
    v_codigo_emp    NUMBER := 124;
    v_emp_rec       emps_retirados%ROWTYPE
BEGIN
    SELECT * INTO v_emp_rec
    FROM emps_retirados
    WHERE codigo = v_codigo_emp;
---
    v_emp_rec.nombre := 'Samira';
    v_emp_rec.empleo := 'Secre';
    v_emp_rec.fec_salida := SYSDATE-2;
---
    UPDATE emps_retirados
    SET ROW = v_emp_rec
    WHERE codigo = v_codigo_emp;
---
    COMMIT;
END;
/
SELECT *
FROM emps_retirados;
/*En este se recuperan los datos del mismo empleado 124 pero ahora de la tabla emps_retirados, luego se les hace algunas modificaciones a los datos y luego se actualiza el registro en la misma tabla; notar que simple luce la sentencia UPDATE con el uso del atributo %ROWTYPE.*/
____________________________________________________________________________________
Colecciones de PL/SQL.
Como se dijo anteriormente, se usan colecciones de PL/SQL cuando se desea almacenar valores del mismo tipo de datos. Este tipo de datos puede ser también de tipo compuesto (por ejemplo, Records).
Por lo tanto, las colecciones se usan para tratar los datos como una sola unidad. Las colecciones son de tres tipos:
• Associative Array O Matriz Asociativa.
• Nested Table.
• VARRAY.

Associative Array(INDEX BY tables).
Un Associative Array es un tipo de colección de PL/SQL, es un tipo de datos compuesto, y es definido por el usuario. Este tipo esta compuesto por conjuntos de pares key-value, entiéndase key como la llave primaria y value como el valor de la fila. Pueden almacenar datos utilizando una llave primaria(key) como el índice, donde la llave primaria no necesariamente tiene que ser secuencial. Los Associative Arrays son también conocidos como INDEX BY tables.

Los Associative Array tienen sólo dos columnas, a las cuales no es posible asignarles nombres:
• La primera columna, de tipo entero o carácter, actúa como la llave primaria.
• La segunda columna, de tipo escalar o Record, contiene los valores.

Ejemplo:
Estructura de los Associative Arrays.
Como se mencionó anteriormente, los Associative Arrays tienen dos columnas. La segunda columna puede contener un valor por cada fila, o varios valores.

Llave primaria única: El tipo de dato de esta columna puede ser:
• Numérico, ya sea BINARY_INTEGER o PLS_INTEGER. Estos dos tipos de datos numéricos requieren menos almacenamiento que los NUMBER, y las operaciones aritméticas sobre ellos son más rápidas que con NUMBER.
• VARCHAR2 o uno de sus subtipos.

Columna "Value": La columna Value puede ser de tipo de dato escalar o tipo de dato Record. Una columna con tipo de dato escalar sólo puede tener un valor por fila, mientras que una columna con tipo de dato Record puede contener varios valores por fila.

Otras características.
• Un Associative Array no se rellena en el momento de la declaración. No contiene llaves primarias o valores, y no se puede inicializar al momento de su declaración.
• Se requiere una sentencias ejecutable explícita para rellenar el Associative Array.
• Al igual que el tamaño de una tabla de Base de Datos, el tamaño de un Associative Array no tiene restricciones. Es decir, el número de filas puede aumentar de forma dinámica para que su Associative Array crezca a medida que se añaden nuevas filas. Tenga en cuenta que las llaves primarias no tienen que ser secuenciales, y pueden ser tanto positiva como negativa.

Sintaxis:
TYPE type_name IS TABLE OF 
    { column_type [NOT NULL] | variable%TYPE [NOT NULL]
    | table.column%TYPE [NOT NULL
    | table%ROWTYPE }
    INDEX BY { PLS_INTEGER | BINARY_INTEGER
    | VARCHAR2(<size>) } ;

identifier     type_name;

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
    TYPE nombre_tab_typ
        IS TABLE OF employees.last_name%TYPE
        INDEX BY PLS_INTEGER;
    TYPE fec_contrato_tab_typ
        IS TABLE OF DATE
        INDEX BY PLS_INTEGER;
    t_nombre            nombre_tab_typ;
    t_fec_contrato     fec_contrato_tab_typ;
BEGIN
    t_nombre(1)   := 'Pepe';
    t_fec_contrato(1) :=  SYSDATE;
--
    SELECT  last_name,
            hire_date
    INTO    t_nombre(6),
            t_fec_contrato(6)
    FROM  employees
    WHERE employee_id = 111;
--
    FOR i IN t_nombre.FIRST..t_nombre.LAST LOOP
      IF t_nombre.EXISTS(i) THEN
        DBMS_OUTPUT.PUT_LINE(i||'. Nombre: '||t_nombre(i)||', Fecha: '||t_fec_contrato(i));
      ELSE
        DBMS_OUTPUT.PUT_LINE('Indice: '||i||' no asignado');
      END IF;
    END LOOP;
END;
/*En este ejemplo creamos un tipo tabla de nombres: nombre_tab_typ y un tipo tabla de fechas: fec_contrato_tab_typ; luego creamos una variable de cada tipo: t_nombre t_fec_contrato; en la sección ejecutable notamos como se asigna valores a dichas variables usando sus indices, lo cual se puede puede hacer manualmente o con una sentencia SELECT; también usamos un FOR LOOP para así recorrer nuestras tablas y mostrar los  valores de los indices no nulos. Nota: el método/función EXISTS es explicado mas adelante.*/
---OUTPUT:
Métodos INDEX BY table.
Un método INDEX BY table es una función incorporada(built-in function) que opera sobre un Associative Array y es invocada usando la notación de punto.

Sintaxis:
table_name.method_name[ (parameters) ]

Método

Descripción
EXISTS(n)
Devuelve TRUE si el índice n en el Associative Array existe.
COUNT
Retorna el numero de elementos contenidos en el  Associative Array .
FIRST
·         Retorna el primer   (mas pequeño) numero de indice del Associative Array.
·         Devuelve NULL si el Associative Array esta vació.
LAST
·         Retorna el último (mas grande) numero de indice del Associative Array.
·         Devuelve NULL si el Associative Array esta vació.
PRIOR(n)
Retorna el numero de indice que antecede al indice n en el Associative Array.
NEXT(n)
Retorna el numero de indice que sigue(sucede) al indice en el Associative Array.
DELETE
·         DELETE remueve todos los elemento del Associative Array.
·         DELETE(n) remueve el indice n en el Associative Array.
·         DELETE(m, n) remueve todos los elementos en el rango m, n del Associative Array.
Ejemplos:
SET SERVEROUTPUT ON
DECLARE
    TYPE dept_tab_typ
      IS TABLE OF departments%ROWTYPE
      INDEX BY PLS_INTEGER;
--
    tabla_dept dept_tab_typ; -- Cada elemento en tabla_dept es un Record.
BEGIN
    SELECT * INTO tabla_dept(1)
    FROM departments
    WHERE department_id = 10;
--
    DBMS_OUTPUT.PUT_LINE('Código: '||tabla_dept(1).department_id ||
                         ', Departamento de '||tabla_dept(1).department_name ||
                         ', Código supervisor: '||tabla_dept(1).manager_id);
END;
/*En el ejemplo creamos un tipo table de Recordsdept_tab_typ y creamos una variable(tabla_dept) de dicho tipo; luego asignamos el registro completo del departamento 10 de la tabla departments al indice 1 de nuestra variable; Posteriormente mostramos algunos de sus campos.*/
---OUTPUT:
SET SERVEROUTPUT ON
DECLARE
    TYPE dept_tab_typ
      IS TABLE OF departments%ROWTYPE
      INDEX BY PLS_INTEGER;
--
    tabla_dept dept_tab_typ; -- Cada elemento en tabla_dept es un Record.
    v_cod_dept  NUMBER(3);
BEGIN
    FOR i IN 1..10 LOOP
        v_cod_dept := i*10;
--
        SELECT * INTO tabla_dept(i)
        FROM departments
        WHERE department_id = v_cod_dept;
    END LOOP;
--
    FOR i IN tabla_dept.FIRST..tabla_dept.LAST LOOP
        DBMS_OUTPUT.PUT_LINE('Código: '||tabla_dept(i).department_id ||
                             ', Departamento de '||tabla_dept(i).department_name ||
                             ', Código supervisor: '||tabla_dept(i).manager_id);
    END LOOP;
--
    DBMS_OUTPUT.PUT_LINE('Cantidad Total: '||tabla_dept.COUNT);
END;
/*Aquí modificamos el ejemplo pasado; le agregamos dos FOR LOOP para así almacenar mas registros en nuestro associative array; Notar el uso de los métodos FIRST, LAST y COUNT..*/
---OUTPUT:
____________________________________________________________________________________
Los NESTED Table.
La funcionalidad de los NESTED Table (Tabla Anidada) es similar a la de los Associative Arrays; sin embargo, hay diferencias en su aplicación.
• Los NESTED Table son un tipo de dato válido a nivel de esquema, pero los Associative Arrays no. Por lo tanto, a diferencia de los Associative Arrays, los NESTED Table se pueden almacenar en la Base de Batos.
• El tamaño de un NESTED Table aumentar de forma dinámica, aunque el tamaño máximo es de 2 GB.
• La "Llave primaria" no puede ser un valor negativo (contrario a los Associative Arrays). Aunque se hace referencia a la primera columna como Llave primaria, no hay tal cosa en un NESTED Table. Mas bien es una columna con números.
• Los elementos pueden ser borrados desde cualquier parte, dejando espacios libres con una Llave primaria no secuencial. Las filas de un NESTED Table no están en ningún orden en particular.
• Al recuperar los valores de una tabla anidada, a las filas se le da subíndices consecutivos a partir de 1.

Nota: Para poder asignar valor a una variable tipo NESTED Table debe hacerlo invocando el nombre del tipo.

Sintaxis:
TYPE type_name IS TABLE OF
 {column_type | variable%TYPE
 | table.column%TYPE} [NOT NULL]
 | table.%ROWTYPE

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
    TYPE typ_lugar
        IS TABLE OF locations.city%TYPE;
--
    v_oficinas typ_lugar;
BEGIN
   v_oficinas := typ_lugar
                        (
                          'San Juan',
                          'Tokyo',
                          'Singapore',
                          'Oakland',
                          'Quito'
                        );
    FOR i IN 1.. v_oficinas.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(v_oficinas(i));
    END LOOP;
END;
/**/
---OUTPUT:
____________________________________________________________________________________
Los VARRAY.
Una matriz de tamaño variable (VARRAY) es similar a un Associative Array, excepto que un VARRAY es limitado en tamaño.
• Un VARRAY es una tabla válida a nivel de esquema.
• Los elementos de tipo VARRAY se llaman VARRAYs.
• Los VARRAYs tienen un límite superior fijo. Se tiene que especificar el límite superior cuando se declaran. Esto es similar a las matrices en lenguaje C. El tamaño máximo de un VARRAY es de 2 GB, como en las tablas anidadas(NESTED Table).
• La distinción entre un NESTED Table y un VARRAY es el modo de almacenamiento físico. Los elementos de un VARRAY se almacenan en línea con los datos de la tabla a menos que el tamaño de la VARRAY sea mayor que 4 KB. Esto contrasta con los NESTED Table, que siempre se almacenan fuera de línea.
• Se puede crear un tipo VARRAY en la Base de Datos utilizando SQL.

Ejemplo:
SET SERVEROUTPUT ON
DECLARE
    TYPE varray_typ 
      IS VARRAY(65) OF VARCHAR2(35);
---
    varray_nombre     varray_typ;
    v_nombre          VARCHAR2(35);
---
    v_cont        NUMBER := 0;
BEGIN
    varray_nombre := varray_typ();
    FOR i IN 111..120 LOOP
---
        SELECT first_name||' '||last_name INTO v_nombre
        FROM employees
        WHERE employee_id = i;
---
        v_cont := v_cont+1;
        varray_nombre.EXTEND;
        varray_nombre(v_cont) := v_nombre;
---
        DBMS_OUTPUT.PUT_LINE('Nombre: '||varray_nombre(v_cont));
    END LOOP;
END;
/*Aquí mostramos el uso de los tipos VARRAYs; Notar que es necesario invocar el constructor(equivalente al nombre del tipo base) para así poder asignar valores a una variable tipo VARRAY y también es necesario invocar el método EXTEND cada vez que se desee agregar un nuevo valor.*/
---OUTPUT:
____________________________________________________________________________________
Resumen de los Tipos Colecciones.
Las Matrices Asociativas(Associative Arrays).
Son conjuntos de pares clave-valor, donde cada clave es única y se utiliza para localizar un valor correspondiente en la matriz. La clave puede ser Numérica o Tipo Carácter. El valor de la matriz puede ser de tipo de dato escalar (valor individual) o de tipo de dato de Record (múltiples valores).

Debido a que las matrices asociativas están destinados para almacenar datos temporales, no se puede utilizar con sentencias SQL como INSERT.

Las Tablas Anidadas(NESTED Table).
Una tabla anidada tiene un conjunto de valores. En otras palabras, es una tabla dentro de una tabla. Las tablas anidadas son ilimitadas; es decir, el tamaño de la tabla puede aumentar de forma dinámica. Las tablas anidadas están disponibles tanto en PL/SQL como en la Base de Datos. Dentro de PL/SQL, las tablas anidadas son como matrices unidimensionales, cuyo tamaño puede aumentar de forma dinámica.

Las Matrices de Tamaño Variable(Varrays).
Son colecciones de elementos homogéneos que tienen un número fijo de elementos (aunque se puede cambiar el número de elementos en tiempo de ejecución). Ellos usan números secuenciales como subíndices. Es posible crear estos tipos en SQL, por lo cual se pueden almacenar en tabla de la Base de Datos.
_____________________________________________________________________________________
Fuente: Oracle Database: PL/SQL Fundamentals