El modelo relacional está diseñado para representar los datos como una serie de tablas con columnas y atributos. Oracle desde la version 8 , incorpora tecnologías orientadas a objetos. En este sentido, permite construir tipos de objetos complejos, entendidos como:
- Capacidad para definir objetos dentro de objetos.
- Cierta capacidad para encapsular o asociar métodos con dichos objetos.
Estructura de un tipo de objeto
Un tipo de objeto representa una entidad del mundo real. Encapsula datos y operaciones, por lo que en la especificación sólo se pueden declarar atributos y métodos, pero no constantes, excepciones, cursores o tipos. Al menos un atributo es requerido y los métodos son opcionales. Se compone de los siguientes elementos:
- Su nombre que sirve para identificar el tipo de los objetos.
- Sus atributos que modelan la estructura y los valores de los datos de ese tipo. Cada atributo puede ser de un tipo de datos básico o de un tipo de usuario.
- Sus métodos (procedimientos o funciones) escritos en lenguaje PL/SQL (almacenados en la BDOR), o escritos en C (almacenados externamente).
Los tipos de objetos actúan como plantillas para los objetos de cada tipo. A continuación vemos un ejemplo de cómo definir el tipo de dato Direccion_T en el lenguaje de definición de datos de Oracle, y cómo utilizar este tipo de dato para definir el tipo de dato de los objetos de la clase de Cliente_T.
CREATE TYPE direccion_t AS OBJECT (
calle VARCHAR2(200),
ciudad VARCHAR2(200),
prov CHAR(2),
codpos VARCHAR2(20));
CREATE TYPE cliente_t AS OBJECT (
clinum NUMBER,
clinomb VARCHAR2(200),
direccion direccion_t,
telefono VARCHAR2(20),
fecha_nac DATE,
MEMBER FUNCTION edad RETURN NUMBER,
PRAGMA
RESTRICT_REFERENCES(edad,WNDS));
Atributos
Como las variables, un atributo se declara mediante un nombre y un tipo. El nombre debe ser único dentro del tipo de objeto (aunque puede reutilizarse en otros objetos) y el tipo puede ser cualquier tipo de Oracle excepto:
- LONG y LONG RAW.
- NCHAR, NCLOB y NVARCHAR2.
- MLSLABEL y ROWID.
- Los tipos específicos de PL/SQL: BINARY_INTEGER (y cualquiera de sus subtipos), BOOLEAN, PLS_INTEGER, RECORD, REF CURSOR, %TYPE y %ROWTYPE.
- Los tipos definidos en los paquetes PL/SQL.
Tampoco se puede inicializar un atributo en la declaración empleando el operador de asignación o la claúsula DEFAULT, del mismo modo que no se puede imponer la restricción NOT NULL. Sin embargo, los objetos se pueden almancenar en tablas de la base de datos en las que sí es posible imponer restricciones.
Métodos
En general, un método es un subprograma declarado en una especificación de tipo mediante la palabra clave MEMBER. El método no puede tener el mismo nombre que el tipo de objeto ni el de ninguno de sus atributos.
Muchos métodos constan de dos partes: especificación y cuerpo. La especificación consiste en el nombre del método, una lista opcional de parámetros y en el caso de funciones un tipo de retorno. El cuerpo es el código que se ejecuta para llevar a cabo una operación específica.
Para cada especificación de método en una especificación de tipo debe existir el correspondiente cuerpo del método. El PL/SQL compara la especificación del método y el cuerpo token a token, por lo que las cabeceras deben coincidir palabra a palabra.
En un tipo de objeto, los métodos pueden hacer referencia a los atributos y a los otros métodos sin cualificador. Los métodos se pueden ejecutar sobre los objetos de su mismo tipo. Si x es una variable PL/SQL que almacena objetos del tipo Cliente_T, entonces x.edad() calcula la edad del cliente almacenado en x. La definición del cuerpo de un método en PL/SQL se hace de la siguiente manera:
CREATE OR REPLACE TYPE BODY cliente_t AS
MEMBER FUNCTION edad RETURN NUMBER IS
a NUMBER;
d DATE;
BEGIN
d:= today();
a:= d.año – fecha_nac.año;
IF (d.mes < fecha_nac.mes) OR
((d.mes = fecha_nac.mes) AND (d.dia < fecha_nac.dia))
THEN a:= a-1;
END IF;
RETURN a;
END;
END;
El parámetro SELF
Todos los métodos de un tipo de objeto aceptan como primer parámetro una instancia predefinida del mismo tipo denominada SELF. Independientemente de que se declare implicita o explícitamente, SELF es siempre el primer parámetro pasado a un método. Por ejemplo, el método transform declara SELF como un parámetro IN OUT:
CREATE TYPE Complex AS OBJECT (
MEMBER FUNCTION transform ( SELF IN OUT Complex ) . . .
El modo de acceso por omisión de SELF, es decir, cuando no se declara explícitamente es:
- En funciones miembro el acceso de SELF es IN.
- En procedimientos, si SELF no se declara, su modo por omisión es IN OUT.
En el cuerpo de un método, SELF denota al objeto a partir del cual se invocó el método. Como muestra el siguiente ejemplo, los métodos pueden hacer referencia a los atributos de SELF sin necesidad de utilizar un cualificador:
CREATE FUNCTION gcd ( x INTEGER , y INTEGER ) RETURN INTEGER AS
-- Encuentra el maximo comun divisor de x e y
ans INTEGER;
BEGIN
IF x < y THEN ans : = gcd ( x , y ) ;
ELSE ans : = gcd ( y , x MOD y ) ;
ENDIF ;
RETURN ans ;
END;
/
CREATE TYPE Rational AS
num INTEGER ,
den INTEGER ,
MEMBER PROCEDURE normalize ,
. . .
) ;
/
CREATE TYPE BODY Rational AS
MEMBER PROCEDURE normalize IS
g INTEGER;
BEGIN
--Estas dos sentencias son equivalentes
g : = gcd ( SELF . num , SELF . den ) ;
g : = gcd (num , den ) ;
num : = num / g ;
den : = den / g ;
END normalize ;
. . .
END;
/
Constructores
En Oracle, todos los tipos de objetos tienen asociado por defecto un método que construye nuevos objetos de ese tipo de acuerdo a la especificación del tipo. El nombre del método coincide con el nombre del tipo, y sus parámetros son los atributos del tipo. Por ejemplo las siguientes expresiones construyen dos objetos con todos sus valores.
direccion_t('Avenida Sagunto', 'Puzol', 'Valencia', 'E-23523')
cliente_t( 2347, 'Juan Pérez Ruíz', direccion_t('Calle Eo', 'Onda', 'Castellón', '34568'), '696-779789', '12/12/1981')
Métodos de comparación
Para comparar los objetos de cierto tipo es necesario indicar a Oracle cuál es el criterio de comparación. Para ello, hay que escoger entre un método MAP u ORDER, debiéndose definir al menos uno de estos métodos por cada tipo de objeto que necesite ser comparado. La diferencia entre ambos es la siguiente:
- Un método MAP sirve para indicar cuál de los atributos del tipo se utilizará para ordenar los objetos del tipo, y por tanto se puede utilizar para comparar los objetos de ese tipo por medio de los operadores de comparación aritméticos (<, >).
El PL/SQL usa esta función para evaluar expesiones booleanas como x > y y para las comparaciones implícitas que requieren las claúsulas DISTINCT, GROUP BY y ORDER BY. Un tipo de objeto puede contener sólo una función de MAP, que necesariamente debe carecer de parámetros y debe devolver uno de los siguientes tipos escalares: DATE, NUMBER, VARCHAR2 y cualquiera de los tipos ANSI SQL (como CHARACTER o REAL). Por ejemplo, la siguiente declaración permite decir que los objetos del tipo cliente_t se van a comparar por su atributo clinum.
CREATE OR REPLACE TYPE BODY cliente_t AS
MAP MEMBER FUNCTION ret_value RETURN NUMBER IS
BEGIN
RETURN clinum
END;
END;
- Un método ORDER utiliza los atributos del objeto sobre el que se ejecuta para realizar un cálculo y compararlo con otro objeto del mismo tipo que toma como argumento de entrada. Este método devolverá un valor negativo si el parámetro de entrada es mayor que el atributo, un valor positivo si ocurre lo contrario y un cero si ambos son iguales. El siguiente ejemplo define un orden para el tipo cliente_t diferente al anterior. Sólo una de estas definiciones puede ser válida en un tiempo dado.
CREATE TYPE cliente_t AS OBJECT (
clinum NUMBER,
clinomb VARCHAR2(200),
direccion direccion_t,
telefono VARCHAR2(20),
fecha_nac DATE,
ORDER MEMBER FUNCTION
cli_ordenados (x IN clientes_t) RETURN INTEGER,
CREATE OR REPLACE TYPE BODY cliente_t AS
ORDER MEMBER FUNCTION cli_ordenados (x IN cliente_t) RETURN INTEGER IS
BEGIN
RETURN clinum - x.clinum; /*la resta de los dos números clinum*/
END;
END;
Veamos otro ejemplo: supongamos que c1 y c2 son objetos del tipo Customer. Una comparación del tipo c1 > c2 invoca al método match automáticamente. El método devuelve un número negativo, cero o positivo que indica que SELF es respectivamente menor, igual o mayor que el otro parámetro:
CREATE TYPE Customer AS OBJECT (
id NUMBER,
name VARCHAR2( 2 0 ) ,
addr VARCHAR2( 3 0 ) ,
ORDER MEMBER FUNCTION match ( c Customer ) RETURN INTEGER
) ;
/
CREATE TYPE BODY Cus tomer AS
ORDER MEMBER FUNCTION match ( c Cus tomer ) RETURN INTEGER IS
BEGIN
IF id < c .id THEN
RETURN -1;
--Cualquier numero negativo .
ELSEIF id > c . id THEN
RETURN 1 ;
--cualquier numero positivo
ELSE
RETURN 0 ;
END IF ;
END;
END;
/
Un tipo de objeto puede contener un único método ORDER, que es una función que devuelve un resultado numérico. Es importante tener en cuenta los siguientes puntos:
- Un método MAP proyecta el valor de los objetos en valores escalares (que son más fáciles de comparar). Un método ORDER simplemente compara el valor de un objeto con otro.
- Se puede declarar un método MAP o un método ORDER, pero no ambos.
- Si se declara uno de los dos métodos, es posible comparar objetos en SQL o en un procedimiento.
- Cuando es necesario ordenar un número grande de objetos es mejor utilizar un método MAP (ya que una llamada por objeto proporciona una proyección escalar que es más fácil de ordenar). Un método ORDER es menos eficiente: debe invocarse repetidamente ya que compara sólo dos objetos cada vez
Si un tipo de objeto no tiene definido ninguno de estos métodos, Oracle es incapaz de deducir cuándo un objeto es mayor o menor que otro.
Sin embargo, sí puede determinar cuándo dos objetos del mismo tipo son iguales. Para ello, el sistema compara el valor de los atributos de los objetos uno a uno:
- Si todos los atributos son no nulos e iguales, Oracle indica que ambos objetos son iguales.
- Si alguno de los atributos no nulos es distinto en los dos objetos, entonces Oracle dice que son diferentes.
- En otro caso, Oracle dice que no puede comparar ambos objetos.
Declaración e inicialización de objetos
Una vez que se ha definido un tipo de objeto y se ha instalado en el esquema de la base de datos, es posible usarlo en cualquier bloque PL/SQL. Las instancias de los objetos se crean en tiempo de ejecución. Estos objetos siguen las reglas normales de ámbito y de instanciación. En un bloque o subprograma, los objetos locales son instanciados cuando se entra en el bloque o subproprograma y dejan de existir cuando se sale. En un paquete, los objetos se instancian cuando se referencia por primera vez al paquete y dejan de existir cuando finaliza la sesión.
Declaración de objetos
Los tipos de objetos se declaran del mismo modo que cualquier tipo interno. Por ejemplo, en el bloque que sigue se declara un objeto r de tipo Racional y se invoca al constructor para asignar su valor.
La llamada asigna los valores 6 y 8 a los atributos num y den respectivamente:
DECLARE
r
Racional ;
BEGIN
r
: = Racional ( 6 , 8 ) ;
DBMS_OUTPUT.
PUT_LINE( r . num) ;
También es posible declarar objetos como parámetros formales de funciones y procedimientos, de modo que es posible pasar objetos a los subprogramas almacenados y de un subprograma a otro. En el siguiente ejemplo, se emplea un objeto de tipo Account para especificar el tipo de dato de un parámetro formal:
DECLARE
. . .
PROCEDURE open_act ( new_acct IN OUT Account ) IS . . .
y en el siguiente ejemplo se declara una función que devuelve un objeto de tipo Account:
DECLARE
. . .
FUNCTION get_act ( act_id IN INTEGER ) RETURN Account IS . . .
Inicialización de objetos
Hasta que se inicializa un objeto, invocando al constructor para ese tipo de objeto, el objeto se dice que es Atómicamente nulo. Esto es, el objeto es nulo, no sólo sus atributos.
Un objeto nulo siempre es diferente a cualquier otro objeto. De hecho, la comparación de un objeto nulo con otro objeto siempre resulta NULL. Del mismo modo, si se asigna un objeto con otro objeto atómicamente nulo, el primero se convierte a su vez en un objeto atómicamente nulo (y para poder utilizarlo debe ser reinicializado).
En resumen, si asignamos el no-valor NULL a un objeto, éste se convierte en atómicamente nulo, como se ilustra en el siguiente ejemplo:
DECLARE
r Racional ;
BEGIN
r Racional : = Racional ( 1 , 2 ) ; --r = 1 / 2
r : = NULL;--r atomicament e nulo
IF r IS NULL THEN . . .-- la condición resulta a TRUE
Una buena práctica de programación consiste en inicializar los objetos en su declaración, como se muestra en el siguiente ejemplo:
DECLARE
r Racional : = Racional ( 2 , 3 ) ;--r = 2 / 3
Objetos sin inicializar en PL/SQL
PL/SQL se comporta del siguiente modo cuando accede a objetos sin inicializar:
- Los atributos de un objeto no inicializado se evalúan en cualquier expresión como NULL.
- Intentar asignar valores a los atributos de un objeto sin inicializar provoca la excepción predefinida ACCESS_INTO_NULL.
- La operación de comparación IS NULL siempre produce TRUE cuando se aplica a un objeto no inicializado o a cualquiera de sus atributos.
Existe por tanto, una sutil diferencia entre objetos nulos y objetos con atributos nulos. El siguiente ejemplo intenta ilustrar esa diferencia:
DECLARE
r Racional ;--r es atomicamente nulo
BEGIN
IF r IS NULL THEN . . . --TRUE
IF r . num IS NULL THEN . . .--TRUE
r : = Racional (NULL , NULL) ; --Inicializar
r . num = 4 ;--Ex i t o : r ya no es atomicamente nulo aunque
--sus atributos son nulos
r : = NULL;--r es de nuevo atomicamente nulo
r . num : = 4 ;--Provoca la excepcion ACCESS_INTO_NULL
EXCEPTION
WHEN ACCESS_INTO_NULL THEN
. . .
END;
/
La invocación de los métodos de un objeto no inicializado está permitida, pero en este caso: SELF toma el valor NULL. Cuando los atributos de un objeto no inicializado se pasan como parámetros IN, se evalúan como NULL. Cuando los atributos de un objeto no inicializado se pasan como parámetros OUT o IN OUT, se produce una excepción si se intenta asignarles un valor.
Acceso a los atributos
Para acceder o cambiar los valores de un atributo se emplea la notación punto (’.’). El siguiente ejemplo ilustra esa notación:
DECLARE
r RAcional : = Racional (NULL , NULL) ;
numerador INTEGER;
denominador INTEGER;
BEGIN
. . .
denominador : = r . den ;
r . num = numerador ;
Los nombres de los atributos pueden encadenarse, lo que permite acceder a los atributos de un tipo de objeto anidado. Por ejemplo, supongamos que definimos los tipos de objeto Address y Student como sigue:
CREATE TYPE Address AS OBJECT (
street VARCHAR2( 30 ) ,
city VARCHAR2( 20 ) ,
state CHAR( 2 ) ,
zip_code VARCHAR2( 5 )
) ;
/
CREATE TYPE Student AS OBJECT (
name VARCHAR2( 20 ) ,
home_address Address ,
phone_number VARCHAR2( 10 ) ,
status VARCHAR2( 10 ) ,
advisor_name VARCHAR2( 20 ) ,
. . .
) ;
/
Observe que zip_code es un atributo de tipo Address y que Address es el tipo de dato del atributo home_address del tipo de objeto Student. Si s es un objeto Student, para acceder al valor de su zip_code se emplea la siguiente notación:
s . home_address . zip_code
Fuente: Departamento de Informática de la Universidad de Valencia.