Existem DBMS que permitem uma chave estrangeira que faz referência a uma exibição (e não apenas tabelas base)?

22

Inspirado por uma pergunta de modelagem do Django: Modelagem de Banco de Dados com várias relações muitos-para-muitos no Django . O design do db é algo como:

CREATE TABLE Book
( BookID INT NOT NULL
, BookTitle VARCHAR(200) NOT NULL
, PRIMARY KEY (BookID)
) ;

CREATE TABLE Tag
( TagID INT NOT NULL
, TagName VARCHAR(50) NOT NULL
, PRIMARY KEY (TagID)
) ;

CREATE TABLE BookTag
( BookID INT NOT NULL
, TagID INT NOT NULL
, PRIMARY KEY (BookID, TagID)
, FOREIGN KEY (BookID)  REFERENCES Book (BookID)
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
) ;

CREATE TABLE Aspect
( AspectID INT NOT NULL
, AspectName VARCHAR(50) NOT NULL
, PRIMARY KEY (AspectID)
) ;

CREATE TABLE TagAspect
( TagID INT NOT NULL
, AspectID INT NOT NULL
, PRIMARY KEY (TagID, AspectID) 
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
, FOREIGN KEY (AspectID)  REFERENCES Aspect (AspectID)
) ;

diagrama db

e o problema é como definir a BookAspectRatingtabela e impor a integridade referencial; portanto, não é possível adicionar uma classificação para uma (Book, Aspect)combinação inválida.

AFAIK, CHECKrestrições complexas (ou ASSERTIONS) que envolvem subconsultas e mais de uma tabela, que poderia resolver isso, não estão disponíveis em nenhum DBMS.

Outra idéia é usar (pseudocódigo) uma visão:

CREATE VIEW BookAspect_view
  AS
SELECT DISTINCT
    bt.BookId
  , ta.AspectId
FROM 
    BookTag AS bt
  JOIN 
    Tag AS t  ON t.TagID = bt.TagID
  JOIN 
    TagAspect AS ta  ON ta.TagID = bt.TagID 
WITH PRIMARY KEY (BookId, AspectId) ;

e uma tabela que possui uma chave estrangeira para a exibição acima:

CREATE TABLE BookAspectRating
( BookID INT NOT NULL
, AspectID INT NOT NULL
, PersonID INT NOT NULL
, Rating INT NOT NULL
, PRIMARY KEY (BookID, AspectID, PersonID)
, FOREIGN KEY (PersonID)   REFERENCES Person (PersonID)
, FOREIGN KEY (BookID, AspectID) 
    REFERENCES BookAspect_view (BookID, AspectID)
) ;

Três perguntas:

  • Existem DBMS que permitem um (possivelmente materializado) VIEWcom um PRIMARY KEY?

  • Existem DBMS que permitem que um FOREIGN KEYque REFERENCESum VIEW(e não apenas uma base TABLE)?

  • Esse problema de integridade poderia ser resolvido de outra forma - com os recursos DBMS disponíveis?


Esclarecimento:

Como provavelmente não existe uma solução 100% satisfatória - e a questão do Django nem sequer é minha! - Estou mais interessado em uma estratégia geral de possível ataque ao problema, não em uma solução detalhada. Portanto, uma resposta como "no DBMS-X isso pode ser feito com gatilhos na tabela A" é perfeitamente aceitável.

ypercubeᵀᴹ
fonte
Postando como um comentário nas suas duas primeiras perguntas - e não necessariamente para você, como tenho certeza que você já sabe -, mas o SQL Server não oferece suporte a chaves primárias ou estrangeiras para exibições.
Aaron Bertrand
@ Aaron: sim, obrigado. Eu li que o Oracle oferece suporte a restrições de PK em visualizações. Mas não tenho certeza se funcionaria nessa situação. E a resposta para a segunda pergunta (sobre FKs para visualizações) provavelmente é negativa no Oracle.
ypercubeᵀᴹ
Mas eu estou interessado em saber se há alguma outra solução (gatilhos, verifique costraints ou outra combinação)
ypercubeᵀᴹ

Respostas:

9

Esta regra de negócios pode ser aplicada no modelo usando apenas restrições. A tabela a seguir deve resolver seu problema. Use-o em vez da sua visão:

    CREATE TABLE BookAspectCommonTagLink
    (  BookID INT NOT NULL
    , AspectID INT NOT NULL
    , TagID INT NOT NULL
--TagID is deliberately left out of PK
    , PRIMARY KEY (BookID, AspectID)
    , FOREIGN KEY (BookID, TagID) 
        REFERENCES BookTag (BookID, TagID)
    , FOREIGN KEY (AspectID, TagID) 
        REFERENCES AspectTag (AspectID, TagID)
    ) ;
AK
fonte
Ah legal. O único problema que posso pensar é a complexidade introduzida na inserção / exclusão de BookTags e TagAspects. Sempre que uma nova BookTag (ou TagAspect) é removida, por exemplo, é necessário fazer uma pesquisa para remover as linhas correspondentes nesta tabela e / ou alterar a TagIDoutra Tag relacionada à mesma combinação BookAspect.
ypercubeᵀᴹ
E uma pesquisa semelhante teria que ser feita para inserir nessas 2 tabelas. Mas regras complexas exigem procedimentos complexos, portanto isso parece realmente bom.
ypercubeᵀᴹ
@ypercube Ao excluir uma tag, é necessário verificar e, possivelmente, mudar para outra tag que vincule o mesmo livro e aspecto. Quando você insere novas tags, no entanto, não há necessidade de fazer verificações até precisar inserir uma classificação.
AK
1
Se a solução de problemas e a pessoa de entrada de dados forem a mesma pessoa, ou se você expor a mensagem de erro ao usuário final, com certeza. Você está pensando demais nas lojas individuais, onde está fazendo tudo.
Aaron Bertrand
4
@AaronBertrand Você acabou de me fazer um grande favor. Estou terminando um artigo intitulado "Desenvolvendo bancos de dados de baixa manutenção" e esqueci de mencionar que os servidores de aplicativos devem registrar as mensagens de erro originais provenientes dos bancos de dados. Acabei de adicionar. Obrigado por lembrar;)
AK
8

Acho que você descobrirá que, em muitos casos, regras comerciais complexas não podem ser impostas apenas pelo modelo. Este é um daqueles casos em que, pelo menos no SQL Server, acho que um gatilho (de preferência um gatilho em vez de um gatilho) serve melhor ao seu propósito.

Aaron Bertrand
fonte
Ei Aaron, você pode explicar por que, neste caso, um gatilho é uma escolha melhor do que uma entidade e algumas restrições?
AK
2
@AlexKuznetsov Claro, porque eu não gastei 17 horas pensando em como implementar isso com várias chaves estrangeiras de várias colunas e toda a lógica extra que pode ser necessária para lidar com a validação e o tratamento de erros de qualquer maneira?
Aaron Bertrand
2
Tenha cuidado com as condições de corrida que uma implementação ingênua de gatilhos pode introduzir. Por exemplo, uma transação pode desconectar um livro da tag e outra ainda achar que é aceitável conectar-se ao aspecto correspondente, simplesmente porque a primeira transação ainda não foi confirmada. As complexidades introduzidas pela resposta do @AlexKuznetsov provavelmente são menores que a complexidade e a fragilidade do "protocolo" de bloqueio necessário para evitar condições de corrida nos gatilhos, IMHO.
Branko Dimitrijevic
8

No Oracle, uma maneira de impor esse tipo de restrição de maneira declarativa seria criar uma exibição materializada que seja configurada para atualizar rapidamente na confirmação, cuja consulta identifica todas as linhas inválidas (ou seja, BookAspectRatinglinhas que não têm correspondência BookAspect_view). Você pode criar uma restrição trivial nessa exibição materializada que seria violada se houver alguma linha na exibição materializada. Isso tem o benefício de minimizar a quantidade de dados que você precisa duplicar na visão materializada. No entanto, pode causar problemas, já que a restrição é aplicada apenas no momento em que você está comprometendo a transação - muitos aplicativos não são gravados para esperar que uma operação de consolidação possa falhar - e porque a violação da restrição pode ser um pouco difícil associar a uma linha ou tabela específica.

Justin Cave
fonte
4

SIRA_PRISE permite isso.

Embora o FK não seja mais chamado de "FK", mas apenas "restrição de banco de dados" e a "visualização" na verdade nem precise ser definida como uma visualização, é possível incluir apenas a expressão que define a visualização na declaração do restrição de banco de dados.

Sua restrição seria algo como

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

e pronto.

Na maioria dos DBMSs do SQL, no entanto, você teria que fazer o trabalho de análise sob sua restrição, determinar como ele pode ser violado e implementar todos os gatilhos necessários.

Erwin Smout
fonte
Eu sei. Reflete o que eu achava importante no momento da redação.
Erwin Smout
3

No PostgreSQL, não consigo imaginar uma solução sem envolver gatilhos, mas certamente pode ser resolvida dessa maneira (seja mantendo uma visão materializada de algum tipo ou um gatilho anterior BookAspectRating). Não há chaves estrangeiras referenciando uma view ( ERROR: referenced relation "v_munkalap" is not a table), muito menos uma chave primária.

dezso
fonte