Implementação no Oracle de um esquema E/R no modelo relacional e objeto-relacional - parte 2

Implementação Objeto-Relacional

Nesta seção é mostrado como pode ser implementado no banco de dados Oracle, através da tecnologia objeto relacional, o exemplo da Figura 01. Em Java, a lista de características de um objeto é chamada de Classe. No banco de dados esta lista de características é um Tipo.

Em um banco de dados queremos e precisamos armazenar os objetos. Sendo assim, precisamos de um “repositório” para armazenamento de objetos. Para resolver esta necessidade, a Oracle criou um novo tipo de tabela no banco de dados. Uma Object Table é uma tabela para armazenamento das instâncias de uma determinada classe. Sendo assim, uma Object Table é uma tabela para armazenamento de objetos, e uma tabela de objetos é construída onde seus campos são instâncias de objetos.

Num modelo Relacional, os relacionamentos são restrições que participam das regras de negócio estabelecidas no banco de dados. Num modelo Objeto-Relacional, utilizamos uma Ref (referência) para estabelecer uma ligação com um objeto específico, ou com uma instância específica de um tipo de objeto.

A expressão Ref difere de um relacionamento estabelecido no modelo Relacional. Ela, por si só, não estabelece uma restrição de integridade, não há impedimento à remoção de uma instância que esteja sendo referenciada por outra. A expressão Ref corresponde a um ponteiro, objetiva acesso mais rápido e não regra de integridade.

O fato de a cláusula Ref, por si só, não garantir a integridade referencial não quer dizer que não possamos ter esta garantia. Podemos optar entre referência sem integridade ou com integridade, de acordo com a situação. A referência pode ser usada apenas como meio de acesso direto. Desta forma temos as seguintes opções:

. Coluna Ref com a cláusula References – simultaneamente endereça e restringe;
.
Coluna Ref sem a cláusula References – apenas endereça;
. Coluna não Ref com a cláusula References – apenas restringe.

Toda vez que incluímos uma restrição temos a opção de determinar se a verificação será feita de imediato ou somente no momento do Commit e se esta situação pode ser alterada. Na cláusula References temos como opção o uso da expressão ON DELETE CASCADE, que indica que se removermos o “pai” da relação os “filhos” também devem ser removidos, ou da expressão ON DELETE SET NULL, que indica que se removermos o “pai” da relação os “filhos” não devem ser removidos, porém, o relacionamento deve ser anulado. Quando não usamos a expressão ON DELETE, indicamos que não desejamos que seja removido nenhum “pai” com “filhos”.

Em um banco de dados Objeto-Relacional, podemos criar uma tabela com uma coluna cujo tipo seja outra tabela. Isto é, tabelas podem ser embutidas em outras tabelas como valores em uma coluna.

O Oracle Server armazena os dados das tabelas aninhadas fora das linhas da tabela-pai, usando uma propriedade (store table) que ficará associada com a coluna da tabela aninhada. A linha-pai, na verdade, contém um identificador único associado com a instância correspondente da tabela aninhada. Não é possível acrescentar novas colunas em tabelas de objetos. Uma tabela aninhada não pode conter outra tabela aninhada.

O tipo de dado de uma tabela aninhada pode ser: um escalar (varchar2, number, date, raw, char, blob, clob, etc.), uma referência a um objeto (Ref), um tipo objeto. Quando seu tipo é escalar, a tabela aninhada descreve uma tabela com uma única coluna, de tipo escalar, chamada “column_value” (nome padrão pré-definido). Quando o tipo de dado é um objeto, a tabela aninhada descreve uma tabela cujas colunas correspondem a atributos do tipo objeto.

Tomando como exemplo a relação TOMADORES, na implementação relacional para o atributo multivalorado Fones é criada uma nova tabela. Na implementação objeto-relacional é criado um tipo TELEFONES_T como uma tabela do tipo char(10), onde o campo alfanumérico tem dez posições. Para não criar uma nova tabela, repetindo os códigos, é criada uma tabela aninhada Fones do tipo TELEFONES_T ao objeto TOMADORES_T. A tabela de objetos TOMADORES do tipo TOMADORES_T, possuirá a tabela aninhada Fones e os telefones de cada tomador são armazenados fisicamente em TELEFONES_OR. O “STORE AS” indica o local físico da tabela aninhada.

As tabelas aninhadas são tabelas onde a primeira forma normal, no processo de normalização, não é respeitada. Ou seja, os domínios dos atributos de uma tabela podem ser atômicos ou ser outra tabela. Dessa forma, tabelas podem ser armazenadas dentro de tabelas. Através de tabelas aninhadas, um objeto complexo pode ser representado por uma única tupla. 

O tipo TOMADORES_T é criado e o CodTomador faz referência, através do tipo Ref, a uma instância de objeto. Tabelas criadas sem o Ref implicam em redundância. Se um tomador solicitasse 5 (cinco) adiantamentos implicaria na repetição de seus dados 5 (cinco) vezes. Tabelas de objetos criadas através do tipo referência permitem que um atributo seja referência a objeto do tipo especificado.

Para criar a tabela representando o relacionamento N:M, a primeira alternativa é criar a tabela REPRESENTAM_OR como no modelo relacional, conforme demonstrado abaixo.

CREATE OR REPLACE TYPE tomadores_t AS OBJECT
(CodTomador          NUMBER   (3),
 NomeTomador         VARCHAR2 (80),
 Fones               telefones_t,
 Situacao            CHAR     (1));

CREATE TABLE tomadores_or OF tomadores_t
(CodTomador          NOT NULL,
 NomeTomador         NOT NULL,
 Situacao            NOT NULL)
NESTED TABLE Fones STORE AS telefones_or
TABLESPACE cpcadata;

CREATE OR REPLACE TYPE localidades_t AS OBJECT
(CodLocalidade       NUMBER   (3),
 NomeLocalidade      VARCHAR2 (80),
 Situacao            CHAR     (1));

CREATE TABLE localidades_or OF localidades_t
(CodLocalidade       NOT NULL,
 NomeLocalidade      NOT NULL,
 Situacao            NOT NULL)
TABLESPACE cpcadata;

CREATE TABLE representam_or
(CodTomador          NUMBER (3) NOT NULL,
 CodLocalidade       NUMBER (3) NOT NULL,
 Situacao            CHAR   (1) NOT NULL)
TABLESPACE cpcadata;

ALTER TABLE representam_or
ADD CONSTRAINT representam_or_pk PRIMARY KEY (CodTomador, CodLocalidade)
USING INDEX TABLESPACE cpcaindx;

ALTER TABLE representam_or
ADD CONSTRAINT representam_or_fk1 FOREIGN KEY (CodTomador)
REFERENCES tomadores_or (CodTomador);

ALTER TABLE representam_or

ADD CONSTRAINT representam_or_fk2 FOREIGN KEY (CodLocalidade)
REFERENCES localidades_or (CodLocalidade);

Outra alternativa é criar um tipo REPRESENTAM_T usando o tipo Ref, onde fará referência às instâncias de objeto TOMADORES_T e LOCALIDADES_T.

CREATE OR REPLACE TYPE representam_t AS OBJECT
(CodTomador          NUMBER   (3),
 CodLocalidade       NUMBER   (3),
 Situacao            CHAR     (1),
 Representam_or_fk1 REF tomadores_t,
 Representam_or_fk2 REF localidades_t);

CREATE TABLE representam_or OF representam_t
(CodTomador          NOT NULL,
 CodLocalidade       NOT NULL,
 Situacao            NOT NULL,
 Representam_or_fk1 WITH ROWID REFERENCES tomadores_or,
 Representam_or_fk2 WITH ROWID REFERENCES localidades_or)
TABLESPACE cpcadata;

A seguir é apresentada a implementação da criação dos tipos.

CREATE OR REPLACE TYPE telefones_t AS TABLE OF CHAR(10);

CREATE OR REPLACE TYPE tomadores_t AS OBJECT
(CodTomador          NUMBER   (3),
 NomeTomador         VARCHAR2 (80),
 Fones               telefones_t,

 Situacao            CHAR     (1));

CREATE OR REPLACE TYPE localidades_t AS OBJECT
(CodLocalidade       NUMBER   (3),
 NomeLocalidade      VARCHAR2 (80),
 Situacao            CHAR     (1));

CREATE OR REPLACE TYPE representam_t AS OBJECT
(CodTomador          NUMBER   (3),
 CodLocalidade       NUMBER   (3),
 Situacao            CHAR     (1),
 Representam_or_fk1 REF tomadores_t,
 Representam_or_fk2 REF localidades_t);
CREATE OR REPLACE TYPE adiantamentos_t AS OBJECT
(NumAdiantamento     NUMBER   (5),
 CodTomador          NUMBER   (3),
 CodLocalidade       NUMBER   (3),
 Data                DATE,
 Adiantamentos_or_fk1 REF tomadores_t,
 Adiantamentos_or_fk2 REF localidades_t);

CREATE OR REPLACE TYPE itens_t AS OBJECT
(NumAdiantamento     NUMBER (5),
 NumItem             NUMBER (5),
 PrazoAplicacao      NUMBER (3),
 DataAplicacao       DATE,
 PrazoPrestacaoConta NUMBER (3),
 DataPrevPrestConta  DATE,
 Valor               NUMBER (10,2),
 NumPrestacaoConta   NUMBER (5),
 DataPrestacaoConta  DATE,
 Itens_or_fk1 REF adiantamentos_t);

A seguir é apresentada a implementação da criação das tabelas.

CREATE TABLE tomadores_or OF tomadores_t
(CodTomador          NOT NULL,
 NomeTomador         NOT NULL,
 Situacao            NOT NULL)
NESTED TABLE Fones STORE AS telefones_or
TABLESPACE cpcadata;

CREATE TABLE localidades_or OF localidades_t
(CodLocalidade       NOT NULL,
 NomeLocalidade      NOT NULL,
 Situacao            NOT NULL)
TABLESPACE cpcadata;

CREATE TABLE representam_or OF representam_t
(CodTomador          NOT NULL,
 CodLocalidade       NOT NULL,
 Situacao            NOT NULL,
 Representam_or_fk1 WITH ROWID REFERENCES tomadores_or,
 Representam_or_fk2 WITH ROWID REFERENCES localidades_or)
TABLESPACE cpcadata;
CREATE TABLE adiantamentos_or OF adiantamentos_t
(NumAdiantamento     NOT NULL,
 CodTomador          NOT NULL,
 CodLocalidade       NOT NULL,
 Data                NOT NULL,
 Adiantamentos_or_fk1 WITH ROWID REFERENCES tomadores_or,
 Adiantamentos_or_fk2 WITH ROWID REFERENCES localidades_or)
TABLESPACE cpcadata;

CREATE TABLE itens_or OF itens_t
(NumAdiantamento     NOT NULL,
 NumItem             NOT NULL,
 PrazoAplicacao      NOT NULL,
 DataAplicacao       NOT NULL,
 PrazoPrestacaoConta NOT NULL,
 DataPrevPrestConta  NOT NULL,
 Valor               NOT NULL,
 NumPrestacaoConta   NULL,
 DataPrestacaoConta  NULL,
 Itens_or_fk1 WITH ROWID REFERENCES adiantamentos_or)
TABLESPACE cpcadata;

A seguir é apresentada a implementação da inserção das chaves primárias.

ALTER TABLE tomadores_or
ADD CONSTRAINT tomadores_or_pk PRIMARY KEY (CodTomador)
USING INDEX TABLESPACE cpcaindx;

ALTER TABLE localidades_or
ADD CONSTRAINT localidades_or_pk PRIMARY KEY (CodLocalidade)
USING INDEX TABLESPACE cpcaindx;

ALTER TABLE representam_or
ADD CONSTRAINT representam_or_pk PRIMARY KEY (CodTomador, CodLocalidade)
USING INDEX TABLESPACE cpcaindx;

ALTER TABLE adiantamentos_or
ADD CONSTRAINT adiantamentos_or_pk PRIMARY KEY (NumAdiantamento)
USING INDEX TABLESPACE cpcaindx;

ALTER TABLE itens_or
ADD CONSTRAINT itens_or_pk PRIMARY Key (NumAdiantamento, NumItem)
USING INDEX TABLESPACE cpcaindx;

A seguir é apresentada a implementação da inserção dos checks

ALTER TABLE tomadores_or
ADD CONSTRAINT tomadores_or_ck1 CHECK (Situacao IN ('A','I'));

ALTER TABLE localidades_or
ADD CONSTRAINT localidades_or_ck1 CHECK (Situacao IN ('A','I'));

ALTER TABLE representam_or
ADD CONSTRAINT representam_or_ck1 CHECK (Situacao IN ('A','I'));

A seguir é apresentada a implementação da criação das sequences.

CREATE SEQUENCE tomadores_or_seq1
MINVALUE 1
MAXVALUE 999
START WITH 1
INCREMENT BY 1;

CREATE SEQUENCE localidades_or_seq1
MINVALUE 1
MAXVALUE 999
START WITH 1
INCREMENT BY 1;

CREATE SEQUENCE adiantamentos_or_seq1
MINVALUE 1
MAXVALUE 999
START WITH 1
INCREMENT BY 1;

CREATE SEQUENCE itens_or_seq1
MINVALUE 1
MAXVALUE 999
START WITH 1
INCREMENT BY 1;

CREATE SEQUENCE itens_or_seq2
MINVALUE 1
MAXVALUE 999
START WITH 1
INCREMENT BY 1;

A seguir é apresentada a implementação da criação dos índices.

CREATE INDEX tomadores_or_idx1
ON tomadores_or (NomeTomador)
TABLESPACE cpcaindx;
CREATE INDEX localidades_or_idx1
ON localidades_or (NomeLocalidade)
TABLESPACE cpcaindx;

CREATE INDEX itens_or_idx1
ON itens_or (NumPrestacaoConta)
TABLESPACE cpcaindx;

CREATE INDEX telefones_or_idx1
ON telefones_or (COLUMN_VALUE)
TABLESPACE cpcaindx;

A seguir é apresentada a implementação da criação da trigger.

CREATE OR REPLACE TRIGGER Verifica_Representatividade_or
BEFORE INSERT ON adiantamentos_or
FOR EACH ROW
DECLARE
  VT_CodTomador NUMBER(3) DEFAULT 0;
BEGIN
  SELECT CodTomador INTO VT_CodTomador
  FROM   representam_or
  WHERE  CodTomador    = :NEW.CodTomador
  AND    CodLocalidade = :NEW.CodLocalidade;
  EXCEPTION
  WHEN OTHERS THEN
    IF SQL%NOTFOUND THEN
       Raise_Application_Error( -20001, 'Não há representatividade entre tomador e localidade');
    END IF;
END;

A seguir é apresentada a implementação da criação das funções.

CREATE OR REPLACE FUNCTION Calcula_Data_Aplicacao_or
(VT_NumAdiantamento NUMBER, VT_Prazo_Aplicacao NUMBER)
RETURN DATE
IS
  RT_Data DATE;
BEGIN
  SELECT Data INTO RT_Data
  FROM   adiantamentos_or
  WHERE  NumAdiantamento = VT_NumAdiantamento;
  RETURN (RT_Data+VT_Prazo_Aplicacao);
END;
CREATE OR REPLACE FUNCTION Calcula_Data_PrestConta_or
(VT_NumAdiantamento NUMBER, VT_Prazo_Aplicacao NUMBER, VT_Prazo_PrestConta NUMBER)
RETURN DATE
IS
  RT_Data DATE;
BEGIN
  SELECT Data INTO RT_Data
  FROM   adiantamentos_or
  WHERE  NumAdiantamento = VT_NumAdiantamento;
  RETURN (RT_Data+VT_Prazo_Aplicacao+VT_Prazo_PrestConta);
END;

A seguir é apresentada a implementação da criação da view.

CREATE OR REPLACE VIEW View_Adiant_Atraso_or
AS
SELECT a.NumAdiantamento,     a.CodTomador,
       a.CodLocalidade,       i.NumItem,
       i.PrazoAplicacao,      i.DataAplicacao,
       i.PrazoPrestacaoConta, i.DataPrevPrestConta,
       i.NumPrestacaoConta,   i.DataPrestacaoConta,
       ROUND(i.DataPrestacaoConta - i.DataPrevPrestConta) "Dias Atraso"
FROM   adiantamentos_or a, itens_or i
WHERE  i.itens_or_fk1       = REF(a)
AND    i.NumPrestacaoConta  IS NOT NULL
AND    i.DataPrestacaoConta > i.DataPrevPrestConta;

A seguir é apresentada a implementação das inserções nas tabelas.

INSERT INTO tomadores_or
VALUES (tomadores_t(tomadores_or_seq1.NEXTVAL,'Afonso
        Lima',telefones_t('9211111111','9222222222'),'A'));
COMMIT;

INSERT INTO TABLE
(SELECT Fones
 FROM   tomadores_or
 WHERE  CodTomador = 001)
VALUES ('9233333333');
COMMIT;

INSERT INTO localidades_or
VALUES (localidades_t(localidades_or_seq1.NEXTVAL,'GERH','A'));
COMMIT;
INSERT INTO representam_or
SELECT representam_t(tomadores_or_seq1.CURRVAL,
       localidades_or_seq1.CURRVAL,'A',REF(t),REF(l))
FROM   tomadores_or t, localidades_or l
WHERE  CodTomador    = 001
AND    Codlocalidade = 001;
COMMIT;

INSERT INTO adiantamentos_or
SELECT adiantamentos_t(adiantamentos_or_seq1.NEXTVAL,
       tomadores_or_seq1.CURRVAL,
       localidades_or_seq1.CURRVAL,
       TO_DATE(TO_CHAR(sysdate,'yyyymmdd'),'yyyymmdd'),
       REF(t),REF(l))
FROM   tomadores_or t, localidades_or l
WHERE  CodTomador    = 001
AND    Codlocalidade = 001;
COMMIT;

INSERT INTO itens_or
SELECT itens_t(adiantamentos_or_seq1.CURRVAL,
       itens_or_seq1.NEXTVAL,30,
Calcula_Data_Aplicacao_or(adiantamentos_or_seq1.CURRVAL,30),30,       Calcula_Data_PrestConta_or(adiantamentos_or_seq1.CURRVAL,30,30),100000,NULL,NULL,REF(a))
FROM   adiantamentos_or a
WHERE  NumAdiantamento = 00001;
COMMIT;

----- Inserção da Prestação de Conta -----
UPDATE itens_or
SET    NumPrestacaoConta  = itens_or_seq2.NEXTVAL,
       DataPrestacaoConta = TO_DATE('20071231','yyyymmdd')
WHERE  NumAdiantamento = 00001
AND    NumiTEM         = 00001;
COMMIT;

----- Testeda Trigger -----
INSERT INTO adiantamentos_or
SELECT adiantamentos_t(00002,002,002,
       TO_DATE(TO_CHAR(sysdate,'yyyymmdd'),'yyyymmdd'),
       REF(t),REF(l))
FROM   tomadores_or t, localidades_or l
WHERE  CodTomador    = 001
AND    Codlocalidade = 001;
ROLLBACK;

A seguir é apresentada a implementação de consultas às tabelas.

SELECT CodTomador, NomeTomador, Fones, Situacao
FROM tomadores_or;

SELECT COLUMN_VALUE Fones
FROM THE (SELECT Fones
          FROM   tomadores_or
         WHERE  CodTomador = 001);

SELECT CodLocalidade, NomeLocalidade, Situacao
FROM localidades_or;

SELECT t.NomeTomador, l.NomeLocalidade
FROM   representam_or r, tomadores_or t, localidades_or l
WHERE  r.representam_or_fk1 = REF(t)
AND    r.representam_or_fk2 = REF(l);

SELECT a.NumAdiantamento, t.NomeTomador,
       l.NomeLocalidade,  a.Data
FROM   adiantamentos_or a, tomadores_or t, localidades_or l
WHERE  a.adiantamentos_or_fk1 = REF(t)
AND    a.adiantamentos_or_fk2 = REF(l);

SELECT a.NumAdiantamento,     i.NumItem,
       i.PrazoAplicacao,      i.DataAplicacao,
       i.PrazoPrestacaoConta, i.DataPrevPrestConta,
       i.Valor,               i.NumPrestacaoConta,
       i.DataPrestacaoConta
FROM   itens_or i, adiantamentos_or a
WHERE  i.itens_or_fk1 = REF(a);

SELECT *
FROM View_Adiant_Atraso_or;

Conclusão

Neste artigo, foi apresentada a implementação de um exemplo no banco de dados Oracle, a partir do seu projeto, na forma relacional e objeto-relacional. De tudo que foi utilizado neste exemplo para o modelo relacional, não foi possível inserir valores default para o modelo objeto-relacional, porque o banco de dados Oracle não permite a modificação de tabelas de objetos. Para a realização deste artigo, utilizei o SmartDraw, o ERwin, o SQL Navigator e o banco de dados Oracle 9i.