Conception de base de données: normalisation d'une relation «(plusieurs-à-plusieurs) -à-plusieurs»

13

Version courte

Je dois ajouter un nombre fixe de propriétés supplémentaires à chaque paire dans une jointure plusieurs-à-plusieurs existante. En passant aux diagrammes ci-dessous, laquelle des options 1 à 4 est la meilleure façon, en termes d'avantages et d'inconvénients, d'y parvenir en étendant le scénario de base? Ou, y a-t-il une meilleure alternative que je n'ai pas envisagée ici?

Version plus longue

J'ai actuellement deux tables dans une relation plusieurs-à-plusieurs, via une table de jointure intermédiaire. Je dois maintenant ajouter des liens supplémentaires vers des propriétés qui appartiennent à la paire d'objets existants. J'ai un nombre fixe de ces propriétés pour chaque paire, bien qu'une entrée dans le tableau des propriétés puisse s'appliquer à plusieurs paires (ou même être utilisée plusieurs fois pour une paire). J'essaie de déterminer la meilleure façon de procéder et j'ai du mal à savoir comment penser la situation. Sémantiquement, il semble que je puisse le décrire comme l'un des éléments suivants également:

  1. Une paire liée à un ensemble d'un nombre fixe de propriétés supplémentaires
  2. Une paire liée à de nombreuses propriétés supplémentaires
  3. Plusieurs (deux) objets liés à un ensemble de propriétés
  4. De nombreux objets liés à de nombreuses propriétés

Exemple

J'ai deux types d'objets, X et Y, chacun avec des ID uniques, et une table de liaison objx_objyavec des colonnesx_id et y_id, qui forment ensemble la clé primaire du lien. Chaque X peut être lié à plusieurs Y et vice versa. Il s'agit de la configuration de ma relation plusieurs-à-plusieurs existante.

Cas de base

Cas de base

Maintenant, en plus, j'ai un ensemble de propriétés définies dans une autre table et un ensemble de conditions dans lesquelles une paire (X, Y) donnée doit avoir la propriété P. Le nombre de conditions est fixe et le même pour toutes les paires. Ils disent essentiellement "Dans la situation C1, la paire (X1, Y1) a la propriété P1", "Dans la situation C2, la paire (X1, Y1) a la propriété P2", et ainsi de suite, pour trois situations / conditions pour chaque paire dans la jointure table.

Option 1

Dans ma situation actuelle il y a exactement trois conditions, et je n'ai aucune raison de penser que d'augmenter, une possibilité est d'ajouter des colonnes c1_p_id, c2_p_idet c3_p_idà featx_featy, en spécifiant une donnée x_idet y_idqui la propriété p_idà une utilisation dans chacun des trois cas .

Option 1

Cela ne me semble pas être une excellente idée, car cela complique le SQL pour sélectionner toutes les propriétés appliquées à une fonctionnalité et ne s'adapte pas facilement à davantage de conditions. Cependant, il impose l'exigence d'un certain nombre de conditions par paire (X, Y). En fait, c'est la seule option ici qui le fait.

Option 2

Créez une table de conditions condet ajoutez l'ID de condition à la clé primaire de la table de jointure.

Option 2

Un inconvénient est qu'il ne spécifie pas le nombre de conditions pour chaque paire. Un autre est que lorsque je ne considère que la relation initiale, avec quelque chose comme

SELECT objx.*, objy.* FROM objx
  INNER JOIN objx_objy ON objx_objy.x_id = objx.id
  INNER JOIN objy ON objy.id = objx_objy.y_id

Je dois ensuite ajouter une DISTINCTclause pour éviter les entrées en double. Cela semble avoir perdu le fait que chaque paire ne devrait exister qu'une seule fois.

Option 3

Créez un nouvel «ID de paire» dans la table de jointure, puis créez une deuxième table de liens entre la première et les propriétés et conditions.

Option 3

Cela semble avoir le moins d'inconvénients, à part le manque d'imposer un nombre fixe de conditions pour chaque paire. Est-il judicieux de créer un nouvel ID qui n'identifie rien d'autre que les ID existants?

Option 4 (3b)

Fondamentalement les mêmes que l'option 3, mais sans la création du champ ID supplémentaire. Ceci est accompli en plaçant les deux ID d'origine dans la nouvelle table de jointure, afin qu'elle contienne des champs x_idet y_id, au lieu de xy_id.

Option 4

Un avantage supplémentaire de ce formulaire est qu'il ne modifie pas les tables existantes (bien qu'elles ne soient pas encore en production). Cependant, il duplique essentiellement une table entière plusieurs fois (ou se sent de cette façon, de toute façon) donc ne semble pas non plus idéal.

Sommaire

Mon sentiment est que les options 3 et 4 sont suffisamment similaires pour que je puisse choisir l'une ou l'autre. J'aurais probablement maintenant, sinon pour l'exigence d'un petit nombre fixe de liens vers les propriétés, ce qui rend l'option 1 plus raisonnable qu'elle ne le serait autrement. Sur la base de quelques tests très limités, l'ajout d'unDISTINCT clause à mes requêtes ne semble pas affecter les performances dans cette situation, mais je ne suis pas sûr que l'option 2 représente la situation ainsi que les autres, en raison de la duplication inhérente provoquée par le placement les mêmes paires (X, Y) sur plusieurs lignes de la table de liens.

L'une de ces options est-elle ma meilleure voie à suivre ou existe-t-il une autre structure que je devrais envisager?

Michael Underwood
la source
Globalement, 1 et 4 semblent les meilleures options, je suis d'accord. Il ne serait pas facile d'appliquer le nombre fixe (3) de propriétés avec l'option 4, mais je pense que c'est faisable.
ypercubeᵀᴹ
Pour la DISTINCTclause, je pensais à une requête comme celle à la fin de # 2, qui relie xet ytraverse xycmais ne fait pas référence à c... Donc, si j'ai (x_id, y_id, c_id)contraint UNIQUEavec des lignes (1,1,1)et (1,1,2), alors SELECT x.id, y.id FROM x JOIN xyc JOIN y, je vais en récupérer deux identiques lignes (1,1), et (1,1).
Michael Underwood
1
Ah ok. Je rejetterais l'option 2 de toute façon. J'irais avec 1 ou 4.
ypercubeᵀᴹ
Plus j'y pense, plus je pense que limiter le nombre de propriétés à exactement trois est la moins importante de mes exigences. À moins de commentaires constructifs supplémentaires dans les prochains temps, j'irai probablement avec le numéro 4 à ce stade. Merci pour votre contribution, @ypercube!
Michael Underwood

Réponses:

7
  • Option 1

    * Cela ne me semble pas une bonne idée, car cela complique le SQL pour sélectionner toutes les propriétés appliquées à une fonctionnalité…

    Cela ne complique pas nécessairement la requête SQL (voir conclusion ci-dessous).

    … Et ne s'adapte pas facilement à plus de conditions…

    Il évolue facilement vers plus de conditions, tant qu'il existe un nombre fixe de conditions et qu'il n'y en a pas des dizaines ou des centaines.

    Cependant, il impose l'exigence d'un certain nombre de conditions par paire (X, Y). En fait, c'est la seule option ici qui le fait. *

    C'est le cas, et bien que vous disiez dans un commentaire que c'est "la moins importante de mes exigences", vous n'avez pas dit que cela n'avait pas d'importance du tout.

  • Option 2

    Un inconvénient est qu'il ne spécifie pas le nombre de conditions pour chaque paire. Un autre est que lorsque je ne considère que la relation initiale… je dois ensuite ajouter une clause DISTINCT pour éviter les entrées en double…

    Je pense que vous pouvez rejeter cette option en raison des complications que vous mentionnez. Le objx_objytableau est susceptible d'être le moteur de certaines de vos requêtes (par exemple, "sélectionner toutes les propriétés appliquées à une fonctionnalité", ce que j'entends par toutes les propriétés appliquées à un objxou objy). Vous pouvez utiliser une vue pour pré-appliquer le DISTINCTdonc il ne s'agit pas de compliquer les requêtes, mais cela va très mal évoluer en termes de performances pour un gain très faible.

  • Option 3

    Est-il judicieux de créer un nouvel ID qui n'identifie rien d'autre que les ID existants?

    Non, ce n'est pas le cas - l'option 4 est meilleure à tous égards.

  • Option 4

    … Il duplique essentiellement une table entière plusieurs fois (ou se sent de cette façon, de toute façon) donc ne semble pas non plus idéal.

    Cette option est très bien - c'est la façon évidente de mettre en place les relations si le nombre de propriétés est variable ou sujet à changement

Conclusion

Ma préférence serait l'option 1 si le nombre de propriétés par objx_objyest susceptible d'être stable, et si vous ne pouvez pas imaginer en ajouter plus d'une poignée supplémentaire. C'est également la seule option qui applique la contrainte «nombre de propriétés = 3» - appliquer une contrainte similaire sur l'option 4 impliquerait probablement l'ajout de c1_p_id… colonnes à la table xy de toute façon *.

Si vous ne vous souciez pas vraiment de cette condition et que vous avez également des raisons de douter que le nombre de propriétés sera stable, choisissez l'option 4.

Si vous ne savez pas laquelle, choisissez l'option 1 - c'est plus simple et c'est certainement mieux si vous avez l'option, comme d'autres l'ont dit. Si vous êtes mis hors option 1 "... car cela complique le SQL pour sélectionner toutes les propriétés appliquées à une fonctionnalité ..." Je suggère de créer une vue pour fournir les mêmes données que la table supplémentaire dans l'option 4:

tables option 1:

create table prop(id integer primary key);
create table objx(id integer primary key);
create table objy(id integer primary key);

create table objx_objy(
  x_id integer references objx
, y_id integer references objy
, c1_p_id integer not null references prop
, c2_p_id integer not null references prop
, c3_p_id integer not null references prop
, primary key (x_id, y_id)
);

insert into prop(id) select generate_series(90,99);
insert into objx(id) select generate_series(10,12);
insert into objy(id) select generate_series(20,22);

insert into objx_objy(x_id,y_id,c1_p_id,c2_p_id,c3_p_id)
select objx.id, objy.id, 90, 91, 90+floor(random()*10)
from objx cross join objy;

vue pour «émuler» l'option 4:

create view objx_objy_prop as
select x_id
     , y_id
     , unnest(array[1,2,3]) c_id
     , unnest(array[c1_p_id,c2_p_id,c3_p_id]) p_id
from objx_objy;

"sélectionner toutes les propriétés appliquées à une entité":

select distinct p_id from objx_objy_prop where x_id=10 order by p_id;

/*
|p_id|
|---:|
|  90|
|  91|
|  97|
|  98|
*/

dbfiddle ici

Jack dit d'essayer topanswers.xyz
la source
-3

Je crois que n'importe laquelle de ces options pourrait fonctionner, mais j'irais avec l'option 1 si le nombre de conditions est vraiment fixé à 3, et l'option 2 si ce n'est pas le cas. Le rasoir d'Occam fonctionne également pour la conception de bases de données, tous les autres facteurs étant égaux, la conception la plus simple est généralement la meilleure.

Bien que si vous souhaitez suivre des règles strictes de normalisation de la base de données, je pense que vous devrez utiliser 2, que le nombre de conditions soit fixe ou non.

Matthew Sontum
la source