viernes, 2 de diciembre de 2016

Tipos de Objetos de Oracle

Objetivos:
• Conocer los Tipos de Objetos de Oracle (Object Types).
---El Concepto.
---Su Estructura.
---Sus Componentes.
• Describir sus usos.


NOTA: Usamos como ejemplo la Base de Datos: ORCL, la cual viene por defecto en cualquier versión de ORACLE.
____________________________________________________________________________________
Conceptos Introductorios
• Programación Orientada a Objetos: es un método de programación basado en una jerarquía de clases y objetos bien definidos que cooperan entre sí con el fin de llevar a cabo una tarea.
• Métodos y Atributos: 
-Los Atributos son similares a las variables, los mismos deben tener un nombre y un tipo de dato. El nombre debe ser único dentro del objeto (pero puede ser reutilizado en otros tipos de objetos).
-Un Método es un subprograma declarado en la especificación del objeto utilizando la palabra clave MEMBER o STATIC. Los métodos no pueden tener el mismo nombre que el objeto o el de cualquiera de sus atributos.
• Sus Usos: 
Los tipos de objetos permiten dividir grandes sistemas en entidades lógicas. Esto le permite crear componentes de software que sean modulares, mantenibles y reutilizables entre proyectos y equipos.

Al asociar el código con los datos, los tipos de objeto mueven el código de mantenimiento de secuencias de comandos SQL y bloques PL/SQL en métodos. En lugar de escribir un procedimiento largo que hace cosas diferentes basadas en algún valor de parámetro, puede definir diferentes tipos de objetos y hacer que cada operación sea ligeramente diferente. Al declarar un objeto del tipo correcto, asegúrese de que sólo puede realizar las operaciones para ese tipo.

Los tipos de objetos permiten un modelado de datos realista. Las entidades y relaciones complejas del mundo real se correlacionan directamente en tipos de objetos. Los tipos de objetos se asignan directamente a las clases definidas en lenguajes orientados a objetos como Java y C++.
____________________________________________________________________________________
Objetos de Oracle
Un tipo de objeto es un tipo de dato compuesto definido por el usuario que encapsula una estructura de datos junto con las funciones y procedimientos necesarios para manipular los datos. Las variables que forman la estructura de datos se llaman atributos. Las funciones y procedimientos que caracterizan el comportamiento del tipo de objeto se conocen como métodos. Un tipo especial de método llamado constructor crea una nueva instancia del tipo de objeto y rellena sus atributos.

Los tipos de objetos deben crearse a través de SQL y almacenarse en una Base de Datos Oracle, donde pueden ser compartidos por muchos programas. Cuando se define un tipo de objeto mediante la sentencia CREATE TYPE, se crea una plantilla abstracta para algún objeto del mundo real. La plantilla especifica los atributos y comportamientos que el objeto necesita en el entorno de la aplicación.[1]

Cabe destacar que la estructura de datos formada por el conjunto de atributos es pública, y por lo tanto visible para los programas clientes. Sin embargo, en programas bien diseñados, estos atributos no son manipulados directamente. En su lugar, se utiliza el conjunto de métodos proporcionados, de modo que los datos se mantienen en un estado adecuado.

Su estructura:
Al igual que un paquete, un tipo de objeto tiene una especificación y un cuerpo. La especificación define la interfaz del objeto; Declara un conjunto de atributos junto con las operaciones (métodos) para manipular los datos. El cuerpo define el código para los métodos.

Toda la información que un programa necesita para usar los métodos está en la especificación. Puede cambiar el cuerpo sin cambiar la especificación, y sin afectar a los programas del cliente.

En la especificación, todos los atributos deben declararse antes que cualquier método. Si una especificación declara únicamente atributos, el cuerpo del objeto es innecesario. No se pueden declarar atributos en el cuerpo. Tenga en cuenta que todas las declaraciones en la especificación del objeto son públicas (visibles fuera del objeto).

Sus componentes:
Un objeto encapsula datos y operaciones. Puede declarar atributos y métodos en la especificación de un objeto, no en constantes, excepciones, cursores o en tipos. Debe declarar al menos un atributo (el máximo es 1000). Los métodos son opcionales.

Los Atributos: Al igual que una variable, se declara un atributo con un nombre y un tipo de dato. El nombre debe ser único dentro del tipo de objeto (pero puede ser reutilizado en otros tipos de objetos). El tipo de dato puede ser cualquier tipo de Oracle excepto:
• LONG y LONG RAW.
• ROWID y UROWID.
• Tipos específicos de PL/SQL como: BINARY_INTEGER (y sus subtipos), BOOLEAN, PLS_INTEGER, RECORD, REF CURSOR, %TYPE, y %ROWTYPE.
• Tipos definidos en Paquetes de PL/SQL.

No puede inicializar un atributo en su declaración utilizando el operador de asignación o la cláusula DEFAULT. Además, no puede imponer la restricción NOT NULL en un atributo. Sin embargo, los objetos pueden almacenarse en tablas de Base de Datos en las que puede imponer restricciones.

La estructura de datos puede ser muy compleja. Por ejemplo, el tipo de dato de un atributo puede ser otro tipo de objeto (conocido como tipo de objeto anidado). Eso le permite crear un tipo de objeto complejo a partir de tipos de objetos más simples. Algunos tipos de objetos como colas, listas y árboles son dinámicos, lo que significa que pueden crecer a medida que se utilizan. Los tipos de objetos recursivos, que contienen referencias directas o indirectas a sí mismos, permiten modelos de datos altamente sofisticados.

Métodos: En general, un método es un subprograma declarado en una especificación de tipo de objeto utilizando la palabra clave MEMBER o STATIC. El método no puede tener el mismo nombre que el tipo de objeto o que cualquiera de sus atributos. Los métodos MEMBER se invocan en instancias y los métodos STATIC se invocan en el tipo de objeto, no en sus instancias.

Al igual que los subprogramas contenidos en paquetes, los métodos tienen dos partes: una especificación y un cuerpo. La especificación consiste del nombre, una lista de parámetros (opcional) y, para las funciones, un tipo de retorno. El cuerpo es el código que se ejecuta para llevar a cabo una tarea específica.

Para cada especificación de un método contenido en una especificación de objeto, debe haber un cuerpo de método contenido en el cuerpo del objeto o el método como tal debe ser declarado NOT INSTANTIABLE, esto último para indicar que el cuerpo sólo está presente en subtipos de este tipo. Para hacer coincidir las especificaciones del método y los cuerpos, el compilador PL/SQL hace una comparación token-by-token de sus encabezados; Por lo cual los encabezados deben coincidir exactamente.

Sentencia CREATE TYPE
Utilizamos la sentencia CREATE TYPE para crear la especificación de un tipo de objeto(Object Type), un tipo de objeto SQLJ(SQLJ Object Type), una matriz de nombres variables(Varray), un tipo de tabla anidada(Nested Table Type) o un tipo de objeto incompleto(Incomplete Object Type). En esta pulicacion nos enfocaremos en los tipos de objeto ya que es el tema en cuestion.

Para crear un tipo de objeto usamos las sentencias CREATE TYPE y CREATE TYPE BODY. La sentencia CREATE TYPE especifica el nombre del objeto, sus atributos, métodos y otras propiedades. La sentencia CREATE TYPE BODY contiene el desarrollo de los métodos que implementan el tipo.

Sintaxis:

Especificación:
CREATE [ OR REPLACE ]
   TYPE [ schema. ]type_name
   [ OID 'object_identifier' ]
   [ invoker_rights_clause ]
   { { IS | AS } OBJECT
   | UNDER [schema.]supertype
   }
   [ sqlj_object_type ]
   [ ( attribute datatype
       [ sqlj_object_type_attr ]
       [, attribute datatype
          [ sqlj_object_type_attr ]...
       [, element_spec
          [, element_spec ]...
       ]
     )
   ]
   [ [ NOT ] FINAL ]
   [ [ NOT ] INSTANTIABLE ] ;
---

Cuerpo:
CREATE [ OR REPLACE ] TYPE BODY [ schema. ]type_name
   { IS | AS }
   { subprogram_declaration
   | map_order_func_declaration
   }
     [, { subprogram_declaration
        | map_order_func_declaration
        }
     ]...
   END;
---
Nota: el tema de los Tipos de Objetos de Oracle es bastante profundo de modo que si tratamos de abarcar todos los conceptos tendríamos que incluir muchos detalles que son vitales por lo cual la publicación seria extensa. Es por esta razón, en esta publicación vamos a tocar los puntos básicos y esenciales del tema en cuestión, esto con el fin de facilitar el entendimiento de los temas, con la intención de expandirlo en futuras publicaciones.
____________________________________________________________________________________
Ejemplos:

CREATE OR REPLACE TYPE  hr.typ_bank_account IS OBJECT
(
    account_num         NUMBER,     --NÚMERO DE CUENTA
    account_holder      VARCHAR2(40),   --TITULAR DE LA CUENTA
    balance             NUMBER,     --SALDO
    interest_rate       NUMBER      --TASA DE INTERÉS
);
/*Creamos el tipo objeto: typ_bank_account el cual hará la función de un cuenta bancaria; Este se podría considerar un tipo de objeto simple ya que no tiene métodos ni columnas que derivan de otros objetos.*/
---
SET SERVEROUTPUT ON
DECLARE
    v_account   hr.typ_bank_account;
BEGIN
    v_account   :=  hr.typ_bank_account
                                          (
                                            1000245,
                                            'Julio Tiburcio Vargas',
                                            60000,
                                            0.015
                                          );
    DBMS_OUTPUT.PUT_LINE
                        (
                            'Banco La Minita, donde su dinero pesa!!!'||CHR(10)||
                            RPAD('-',40,'-')||CHR(10)||
                            'Detalles de Cuenta:'||CHR(10)||
                            'Cliente: '||v_account.account_holder||CHR(10)||
                            'Cuenta#: '||v_account.account_num||CHR(10)||
                            'Balance Disponible: '||v_account.balance||CHR(10)||
                            'Tasa de Interés: '||v_account.interest_rate*100||'% Anual'
                        );
END;
/*Este bloque implementa el tipo de objeto: hr.typ_bank_account antes creado; Notar que para asignar nuevos valores a un variable de ese tipo, es necesario invocar su constructor(nombre del tipo objeto) pasandole valores a cada uno de sus atributos, mientras que para extraer los valores almacenados, se utiliza el nombre de la variable seguido de un punto '.' mas el nombre del atributo en cuestión.*/
---OUTPUT:
A Continuación creamos algunos otros Tipos de Objetos que incluiremos en el Objeto antes creado, para así hacerlo un poco más complejo.

CREATE OR REPLACE TYPE  hr.typ_account_holder IS OBJECT
(
    identity_id         NUMBER,     --NÚMERO DE IDENTIDAD
    first_name          VARCHAR2(20),  --NOMBRE
    last_name           VARCHAR2(20),  --APELLIDO
    sex                 CHAR(1),
    birth_date          DATE,           --FECHA DE NACIMIENTO
    address             VARCHAR2(100),  --DIRECCIÓN
    contact_number      VARCHAR2(10),   --NÚMERO DE CONTACTO
    email               VARCHAR2(60)
);
/*Creamos el tipo de objeto: typ_account_holder que hará la función de un titular de una cuenta bancaria.*/
---
CREATE OR REPLACE TYPE  hr.typ_bank_account IS OBJECT
(
    account_num         NUMBER,     --NÚMERO DE CUENTA
    account_holder      hr.typ_account_holder,   --TITULAR DE LA CUENTA
    balance             NUMBER,     --SALDO
    interest_rate       NUMBER      --TASA DE INTERÉS
);
/*Ahora Re-Creamos el tipo de objetotyp_bank_account con el atributo: account_holder del tipo de dato: hr.typ_account_holder(Object Type). Nota: si se presenta la necesidad de modificar /dropear el tipo de objeto: typ_account_holder es requerido hacer DROP del tipo: typ_bank_account, ya que este último es una dependencia del primero.*/
---
SET SERVEROUTPUT ON
DECLARE
    v_holder    hr.typ_account_holder;
    v_account   hr.typ_bank_account;
BEGIN
    v_holder    :=  hr.typ_account_holder
                                            (
                                                '01201087659',
                                                'Julio',
                                                'Tiburcio Vargas',
                                                'M',
                                                TO_DATE('25/02/1984','DD/MM/YYYY'),
                                                'Calle la Palma #3, El Higuero, San Cristóbal, RD.',
                                                '8090002277',
                                                'jtibur_v@magicplsql.com'
                                            );

    v_account   :=  hr.typ_bank_account
                                          (
                                            1000245,
                                            v_holder,
                                            60000,
                                            0.015
                                          );
    DBMS_OUTPUT.PUT_LINE
                        (
                            'Banco La Minita, donde su dinero pesa!!!'||CHR(10)||
                            RPAD('=',40,'=')||CHR(10)||
                            'Datos del Titular:'||CHR(10)||
                            'Nombre: '||v_account.account_holder.first_name||' '||v_account.account_holder.last_name||CHR(10)||
                            'Sexo: '||v_account.account_holder.sex||CHR(10)||
                            'Edad: '||     --Edad = A los meses entre birth_date y hoy dividido por 12
TRUNC(
MONTHS_BETWEEN(
SYSDATE, v_account.account_holder.birth_date)/12
)||CHR(10)
                        );

    DBMS_OUTPUT.PUT_LINE
                        (
                            RPAD('-',40,'-')||CHR(10)||
                            'Detalles de Cuenta:'||CHR(10)||
                            'Cuenta#: '||v_account.account_num||CHR(10)||
                            'Balance Disponible: '||v_account.balance||CHR(10)||
                            'Tasa de Interés: '||v_account.interest_rate*100||'% Anual'
                        );
END;
/*Este ejemplo usa el tipo de objeto: hr.typ_bank_account el cual contiene un atributo(account_holder) de tipo objeto: hr.typ_account_holder. Notar que se crearon dos variables tipo objeto: hr.typ_account_holder y hr.typ_bank_account  respectivamente, para así poder asignar valores al atributo en tipo objeto. El modo de extracción de los datos de un atributo tipo objeto seria como se muestra en el ejemplo: nombre_variable.nombre_atributo.nombre_atributo.*/
---OUTPUT:
CREATE OR REPLACE TYPE  hr.typ_account_details IS OBJECT
(
    account_number      NUMBER,         --NÚMERO DE CUENTA
    account_title       VARCHAR2(30),   --TITULO DE LA CUENTA
    account_type        CHAR(1),         --TIPO DE CUENTA
    open_date           DATE,       --FECHA APERTURA
    balance             NUMBER,     --SALDO
    last_tranc          CHAR(1),     --ÚLTIMA TRANSACCIÓN
    last_tranc_date     DATE,       --FECHA ÚLTIMA TRANSACCIÓN
    interest_rate       NUMBER      --TASA DE INTERÉS
);
/*Creamos el Tipo: hr.typ_account_details el cual representara los detalles de una cuenta bancaria.*/
---
CREATE OR REPLACE TYPE hr.t_account_details
        IS TABLE OF hr.typ_account_details;
/*Luego creamos un tipo tabla: hr.t_account_details que contendrá registros del tipo: hr.typ_account_details antes creado. */
---
CREATE OR REPLACE TYPE  hr.typ_bank_account IS OBJECT
(
    account_holder      hr.typ_account_holder,   --TITULAR DE LA CUENTA
    account             hr.t_account_details     --CUENTA
);
/*Por último re-creamos el tipo: hr.typ_bank_account para que contenga dos campos referentes a los tipos antes creados(hr.typ_account_holder y hr.t_account_details)*/
---
SET SERVEROUTPUT ON
DECLARE
    v_account       hr.typ_bank_account;
    v_holder        hr.typ_account_holder;
    v_acc_detail    hr.t_account_details;

    v_acc_type      CHAR(1);
    v_open_date     DATE;
    v_int_rate      NUMBER(3,3);
BEGIN
    v_holder    :=  hr.typ_account_holder
                                            (
                                                '01201087659',
                                                'Julio',
                                                'Tiburcio Vargas',
                                                'M',
                                                TO_DATE('25/02/1984','DD/MM/YYYY'),
                                                'Calle la Palma #3, El Higuero, San Cristóbal, RD.',
                                                '8090002277',
                                                'jtibur_v@magicplsql.com'
                                            );

    v_acc_detail    :=  hr.t_account_details();  --Primero se inicializa el constructor para poder asignar datos al tipo tabla.

    FOR i IN 1..3 LOOP
        CASE
            WHEN i<2 THEN
                v_acc_type  :=  'D';       --DÉBITO
                v_open_date :=  TO_DATE('01/04/2002','DD/MM/YYYY');
                v_int_rate  :=  .015;
            WHEN i>2 THEN
                v_acc_type  :=  'P';       --PREPAGO
                v_open_date :=  TO_DATE('09/09/2012','DD/MM/YYYY');
                v_int_rate  :=  .16;
            ELSE
                v_acc_type  :=  'C';       --CRÉDITO
                v_open_date :=  TO_DATE('07/07/2005','DD/MM/YYYY');
                v_int_rate  :=  .06;
        END CASE;

        v_acc_detail.EXTEND;     --Agrega un nuevo registro a la Tabla.
        v_acc_detail(i) :=  hr.typ_account_details
                                                     (
                                                        1000244+i,
                                                        'Cuenta '||i,
                                                        v_acc_type,
                                                        v_open_date,
                                                        TRUNC(DBMS_RANDOM.value(60000,150000),2),
                                                        'R',    --Retiro
                                                        SYSDATE - i,
                                                        v_int_rate
                                                     );
    END LOOP;

    v_account   :=  hr.typ_bank_account
                                          (
                                            v_holder,
                                            v_acc_detail
                                          );

     --Para extraer los datos:
    DBMS_OUTPUT.PUT_LINE
                       (
                            'Cliente: '||v_account.account_holder.first_name||' '||v_account.account_holder.last_name||CHR(10)||
                            RPAD('=',35,'=')||CHR(10)||
                            'Cuentas: '||CHR(10)||
                            RPAD('-',35,'-')||CHR(10)
                       );

    FOR i IN v_account.account.FIRST..v_account.account.LAST LOOP    --Del primero al Último
        DBMS_OUTPUT.PUT_LINE
                           (
                                'Número : '||v_account.account(i).account_number||CHR(10)||
                                'Tipo : '||v_account.account(i).account_type||CHR(10)||
                                'Fecha Apertura : '||v_account.account(i).open_date||CHR(10)||
                                'Balance : '||v_account.account(i).balance||CHR(10)||
                                'Tasa de Interés : '||v_account.account(i).interest_rate*100||'% Anual'||CHR(10)||
                                '...'
                           );
    END LOOP;
END;
/*Este ejemplo podría considerarse un poco mas riguroso, pero en realidad es mas de lo mismo. Para poder asignar valores a una variable de tipo objeto tabla es necesario también invocar su constructor sin parámetros(esto porque no tiene atributos), luego es necesario invocar su método EXTEND que agrega un registro en la tabla, posteriormente para asignar los valores a cada atributo del registro, es necesario invocar su constructor como tal. Notar que es necesario indicar el registro de la variable tipo tabla al cual se desea agregar los datos: variable(índice) :=. El medio de extracción es igual: variable(índice).nombre_atributo.*/
---OUTPUT:
____________________________________________________________________________________
Sentencia DROP TYPE
Utilice la sentencia DROP TYPE para eliminar la especificación y el cuerpo de un Tipo de Objeto, un Varray o un Tipo de Tabla Anidada(Nested Table).

Sintaxis:
DROP TYPE [ schema. ] type_name [ FORCE | VALIDATE ];

Tenga en cuenta que sólo puede hacer DROP de tipos sin dependencias de otros tipos o de tablas(solo aplica si no se especifica FORCE ).

• Cláusula FORCE: Especifique FORCE para eliminar el Tipo incluso si tiene Objetos de Base de Datos dependientes. Oracle marca UNUSED todas las columnas dependientes del tipo que se va a eliminar, y esas columnas se vuelven inaccesibles.

Nota: Oracle no recomienda usar FORCE, esta operación no es recuperable y podría hacer que los datos de las tablas o columnas dependientes se vuelvan inaccesibles.

• Cláusula VALIDATE: Si especifica VALIDATE al hacer DROP de un Tipo, Oracle Verifica las instancias almacenadas de este tipo en columnas sustituibles de cualquiera de sus supertipos(tipos padre). Si no se encuentran tales instancias, se completa la operación de eliminación.

Esta cláusula sólo es significativa para los subtipos(tipos hijo). Oracle recomienda el uso de esta opción para eliminar con seguridad subtipos que no tienen ningún tipo explícito o dependencias de tabla.

Ejemplos:
DROP TYPE hr.typ_bank_account;
DROP TYPE hr.typ_account_holder;
DROP TYPE hr.t_account_details;
DROP TYPE hr.typ_account_details;
____________________________________________________________________________________
Entendemos prudente concluir esta publicación hasta este punto. Como ya planteamos, el tema de los Tipo de Objetos de Oracle es inmenso, por lo cual, tratar de abarcarlo todo en una solo entrega seria abrumador.

En una futura entrega tocaremos todo lo referente a los Métodos de un Tipo Objeto.
____________________________________________________________________________________
Fuentes: https://docs.oracle.com/cd/B14117_01/appdev.101/b10807/10_objs.htm#CHDEFBEA