Existe-t-il un SGBD qui autorise une clé étrangère qui fait référence à une vue (et pas seulement des tables de base)?

22

Inspiré d'une question de modélisation Django: Modélisation de base de données avec plusieurs relations plusieurs-à-plusieurs dans Django . Le db-design est quelque chose comme:

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)
) ;

diagramme db

et le problème est de savoir comment définir la BookAspectRatingtable et appliquer l'intégrité référentielle, de sorte que l'on ne peut pas ajouter de note pour une (Book, Aspect)combinaison non valide.

AFAIK, les CHECKcontraintes complexes (ou ASSERTIONS) qui impliquent des sous-requêtes et plus d'une table, qui pourraient éventuellement résoudre ce problème, ne sont disponibles dans aucun SGBD.

Une autre idée est d'utiliser (pseudocode) une vue:

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) ;

et une table qui a une clé étrangère vers la vue ci-dessus:

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)
) ;

Trois questions:

  • Existe-t-il des SGBD qui permettent un (éventuellement matérialisé) VIEWavec un PRIMARY KEY?

  • Y at - il un SGBD qui permettent un FOREIGN KEYque REFERENCESun VIEW(et non seulement une base TABLE)?

  • Ce problème d'intégrité pourrait-il être résolu autrement - avec les fonctionnalités de SGBD disponibles?


Clarification:

Puisqu'il n'y a probablement pas de solution 100% satisfaisante - et la question Django n'est même pas la mienne! - Je suis plus intéressé par une stratégie générale d'attaque possible du problème, pas par une solution détaillée. Ainsi, une réponse comme «dans SGBD-X, cela peut être fait avec des déclencheurs sur la table A» est parfaitement acceptable.

ypercubeᵀᴹ
la source
Publier en tant que commentaire à vos deux premières questions - et pas nécessairement pour vous, comme je suis sûr que vous le savez déjà - mais SQL Server ne prend pas en charge les clés primaires ou étrangères pour les vues.
Aaron Bertrand
@Aaron: oui, merci. J'ai lu qu'Oracle prend en charge les contraintes de PK dans les vues. Mais je ne sais pas si cela fonctionnerait dans cette situation. Et la réponse à la 2ème question (sur les FK aux vues) est probablement négative dans Oracle.
ypercubeᵀᴹ
Mais je suis intéressé à savoir s'il existe une autre solution (déclencheurs, contraintes de vérification ou autre combo)
ypercubeᵀᴹ

Réponses:

9

Cette règle métier peut être appliquée dans le modèle en utilisant uniquement des contraintes. Le tableau suivant devrait résoudre votre problème. Utilisez-le à la place de votre vue:

    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
la source
Oh sympa. Le seul problème que je peux penser est la complexité introduite dans l'insertion / la suppression de BookTags et de TagAspects. Chaque fois qu'un nouveau BookTag (ou TagAspect) est supprimé, par exemple, une recherche doit être effectuée pour supprimer les lignes correspondantes dans ce tableau et / ou le remplacer TagIDpar un autre Tag lié à la même combinaison BookAspect.
ypercubeᵀᴹ
Et une recherche similaire devrait être effectuée pour l'insertion dans ces 2 tableaux. Mais les règles complexes nécessitent des procédures complexes, donc cela semble vraiment bien.
ypercubeᵀᴹ
@ypercube Lorsque vous supprimez une balise, vous devez vérifier et éventuellement passer à une autre balise reliant le même livre et le même aspect. Cependant, lorsque vous insérez de nouvelles balises, il n'est pas nécessaire d'effectuer de vérification jusqu'à ce que vous ayez besoin d'insérer une évaluation.
AK
1
Si l'utilitaire de résolution des problèmes et la personne chargée de la saisie des données sont la même personne, ou si vous exposez le message d'erreur à l'utilisateur final, bien sûr. Vous pensez trop aux magasins pour une personne où vous faites tout.
Aaron Bertrand
4
@AaronBertrand Vous venez de me rendre un immense service. Je termine un article intitulé "Développement de bases de données à faible maintenance", et j'ai oublié de mentionner que les serveurs d'applications doivent enregistrer les messages d'erreur d'origine provenant des bases de données. Je viens de l'ajouter. Merci de l'avoir rappelé;)
AK
8

Je pense que vous constaterez que dans de nombreux cas, des règles commerciales complexes ne peuvent pas être appliquées via le seul modèle. C'est l'un de ces cas où, au moins dans SQL Server, je pense qu'un déclencheur (de préférence un au lieu d'un déclencheur) sert mieux votre objectif.

Aaron Bertrand
la source
Hey Aaron, pouvez-vous expliquer pourquoi dans ce cas un déclencheur est un meilleur choix qu'une entité et quelques contraintes?
AK
2
@AlexKuznetsov Bien sûr, parce que je n'ai pas passé 17 heures à réfléchir à la façon de l'implémenter avec plusieurs clés étrangères multi-colonnes, et à toute la logique supplémentaire qui pourrait être requise pour gérer la validation et la gestion des erreurs de toute façon?
Aaron Bertrand
2
Faites attention aux conditions de concurrence qu'une implémentation naïve des déclencheurs pourrait introduire. Par exemple, une transaction peut déconnecter un livre de la balise et une autre pense toujours qu'il est OK de se connecter à l'aspect correspondant, simplement parce que la première transaction n'a pas encore été validée. Les complexités introduites par la réponse @AlexKuznetsov sont probablement inférieures à la complexité et à la fragilité du "protocole" de verrouillage nécessaire pour empêcher les conditions de concurrence dans les déclencheurs, à mon humble avis.
Branko Dimitrijevic
8

Dans Oracle, une façon d'imposer ce type de contrainte de manière déclarative serait de créer une vue matérialisée qui est définie pour s'actualiser rapidement lors de la validation dont la requête identifie toutes les lignes non valides (c'est-à-dire les BookAspectRatinglignes sans correspondance BookAspect_view). Vous pouvez ensuite créer une contrainte triviale sur cette vue matérialisée qui serait violée s'il y avait des lignes dans la vue matérialisée. Cela a l'avantage de minimiser la quantité de données que vous devez dupliquer dans la vue matérialisée. Cependant, cela peut poser des problèmes, car la contrainte n'est appliquée qu'au moment où vous validez la transaction - de nombreuses applications ne sont pas écrites pour s'attendre à ce qu'une opération de validation puisse échouer - et parce que la violation de la contrainte peut être quelque peu difficile à associer à une ligne ou une table particulière.

Justin Cave
la source
4

SIRA_PRISE permet cela.

Bien que le FK ne soit plus appelé "FK", mais simplement "contrainte de base de données", et la "vue" n'a en fait même pas besoin d'être définie comme une vue, vous pouvez simplement inclure l'expression définissant la vue dans la déclaration de la contrainte de base de données.

Votre contrainte ressemblerait à quelque chose

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

et tu as fini.

Dans la plupart des SGBD SQL, cependant, vous devrez effectuer le travail d'analyse sur votre contrainte, déterminer comment elle peut être violée et implémenter tous les déclencheurs nécessaires.

Erwin Smout
la source
Je sais. Cela reflète ce que je pensais important au moment de la rédaction.
Erwin Smout
3

Dans PostgreSQL, je ne peux pas imaginer une solution sans impliquer de déclencheurs, mais elle peut certainement être résolue de cette façon (que ce soit en maintenant une vue matérialisée d'une sorte ou un déclencheur avant BookAspectRating). Il n'y a pas de clé étrangère référençant une vue ( ERROR: referenced relation "v_munkalap" is not a table), encore moins une clé primaire.

dezso
la source