Em 25 de agosto de 2016 13:44, Guimarães Faria Corcete DUTRA, Leandro
<l...@dutras.org> escreveu:
> Mas de fato há situações em que uma chave pode chegar a cobrir todos
> os atributos (naturais) de uma relação (não confundir com
> relacionamento).

E eu avalizo totalmente esta declaração. E ainda quero citar um
exemplo do porquê chaves naturais compostas DEVEM ser utilizadas
sempre que possível.

Vou citar um exemplo de um software de reservas desenvolvido para a
UTFPR campus DV e que está em uso agora em 2 campus. Inicialmente toda
a camada de acesso a dados utilizava Hibernate (JSF + Hibernate +C3P0)
com PostgreSQL 9.4 usando os famigerados "IDs" e chaves artificiais
autoincremento para tudo, para facilitar a integração com Hibernate.

Enquanto o software esteve rodando para 1 campus apenas (Dois
Vizinhos), não houveram muitos problemas.

Para encurtar o papo, quando começou a importação de dados de um tal
aSc TimeTables que define reservas de salas e horários por disciplinas
e turmas, começou a dor de cabeça. Os usuários importavam 2x o mesmo
XML e os "ID's" por não serem chaves naturais permitiam a duplicação
dos dados.

Pior ficou quando fiz testes para inclusão de um outro campus...

Um item que estava cadastrado para o campus DV (uma sala de aula, por
exemplo) poderia ser reservado em outro campus (erro de lógica). As
FK's e PK's não garantiam integridade lógica, apenas integridade
referencial (filho sem pai). A ideia central do sistema que era ficar
hospedado em um servidor apenas caiu por terra, foi necessário então
solicitar ao outro campus que hospedasse uma nova instância do
aplicativo.

Comecei a trabalhar no projeto com afinco. Refiz todo o modelo usando
chaves naturais e abandonei o Hibernate pelo sql2o. Deu um pouco mais
de trabalho no começo, mas depois tudo fluiu com uma naturalidade
fantástica. Ainda estou testando a nova versão e logo farei a migração
dos dados.

Como exemplo à declaração do Dutra, a tabela RESERVA_ITEM_AUTORIZACAO
possui 7 campos em sua chave primária, todos naturais que são todos os
campos da tabela. Ou seja, é uma tabela onde todos os campos são parte
da chave primária, sendo uma tabela "clássica" de relacionamento N*N.

Finalizando minha "história", com exceção de um índice único e 2
TRIGGERs para tratar horários conflitantes, toda a integridade lógica
ficou GARANTIDA apenas pelo uso das FK's e PK's com atributos
naturais, mesmo que com vários campos. E o número de FK's foi reduzido
para 1 terço do que havia antes, pois a propagação das chaves
compostas garante a integridade com a tabela "pai" de todos os níveis
superiores.

Abaixo segue o modelo atual no qual estou trabalhando (omiti demais
campos que não são chave):

CREATE TABLE pessoa    (
    /* PK - chave natural - código de cada servidor público */
    matricula        VARCHAR(20) NOT NULL,
    (...)
);

CREATE TABLE instituicao (
    /* PK - chave natural - código do MEC */
    sigla             VARCHAR(20) NOT NULL,
    (...)
);


CREATE TABLE campus (
    /* PK e FK tabela INSTITUICAO */
    sigla_instituicao     VARCHAR(20) NOT NULL,

    /* PK - chave natural - código do MEC */
    sigla_campus         VARCHAR(20) NOT NULL,
    (...)
);

/* "Cadastro" de itens */
CREATE TABLE item_reserva (
    /* PK e FK tabela CAMPUS */
    sigla_instituicao     VARCHAR(20) NOT NULL,

    /* PK e FK tabela CAMPUS */
    sigla_campus         VARCHAR(20) NOT NULL,

    /* PK - chave natural - código de patrimonio interno do campus */
    codigo_patrimonio     VARCHAR(20) NOT NULL,
    (...)
);

CREATE TABLE reserva (
    /* PK e FK tabela ITEM_RESERVA */
    sigla_instituicao     VARCHAR(20) NOT NULL,
    /* PK e FK tabela ITEM_RESERVA */
    sigla_campus         VARCHAR(20) NOT NULL,
    /* PK - chave natural */
    data_reserva_inicio    TIMESTAMP NOT NULL,
    /* PK - chave natural */
    data_reserva_fim    TIMESTAMP NOT NULL,
    /* PK e FK tabela PESSOA */
    matricula_usuario    VARCHAR(20) NOT NULL,
    (...)
);


/* Cada reserva pode incluir mais de um item, portanto aqui fica
   separado da tabela reserva

   Há aqui um indice único entre os campos
    sigla_instituicao,
    sigla_campus,
    data_reserva_inicio,
    codigo_patrimonio

   Também há 2 triggers um pouco mais complexos que não permitem
   horários conflitantes, algo impossível de tratar apenas com FKs.

   Motivo: um mesmo item não pode ser reservado 2x na mesma
   data e hora, ou enquanto uma reserva ainda está ativa */
CREATE TABLE reserva_item (
    /* PK e FK tabela ITEM_RESERVA */
    sigla_instituicao     VARCHAR(20) NOT NULL,
    /* PK e FK tabela ITEM_RESERVA */
    sigla_campus         VARCHAR(20) NOT NULL,
    /* PK - chave natural */
    data_reserva_inicio    TIMESTAMP NOT NULL,
    /* PK - chave natural */
    data_reserva_fim    TIMESTAMP NOT NULL,
    /* PK e FK tabela PESSOA */
    matricula_usuario    VARCHAR(20) NOT NULL,
    /* PK e FK tabela ITEM_RESERVA */
    codigo_patrimonio     VARCHAR(20) NOT NULL,
    (...)
);

/* Alguns itens possuem um ou mais servidores responsáveies por
   autorizar a reserva de um item (por exemplo, o ginásio
   de esportes só pode ser autorizado pelo Diretor do
   campus ou pelo vice-Diretor. Esta tabela armazena quem foi que
   autorizou "aquela" determinada reserva solicitada. */
CREATE TABLE reserva_item_autorizacao (
    /* PK e FK tabela ITEM_RESERVA */
    sigla_instituicao     VARCHAR(20) NOT NULL,
    /* PK e FK tabela ITEM_RESERVA */
    sigla_campus         VARCHAR(20) NOT NULL,
    /* PK - chave natural */
    data_reserva_inicio    TIMESTAMP NOT NULL,
    /* PK - chave natural */
    data_reserva_fim    TIMESTAMP NOT NULL,
    /* PK e FK tabela PESSOA */
    matricula_usuario    VARCHAR(20) NOT NULL,
    /* PK e FK tabela ITEM_RESERVA */
    codigo_patrimonio     VARCHAR(20) NOT NULL,
    /* PK e FK tabela PESSOA */
    matricula_autorizacao    VARCHAR(20) NOT NULL
);



TIAGO J. ADAMI
_______________________________________________
pgbr-geral mailing list
pgbr-geral@listas.postgresql.org.br
https://listas.postgresql.org.br/cgi-bin/mailman/listinfo/pgbr-geral

Responder a