Modèle de conception - l'une des nombreuses tables parentales

13

Je rencontre une situation dans la base de données assez fréquemment où une table donnée peut FK vers l'une des nombreuses tables parentales différentes. J'ai vu deux solutions au problème, mais aucune n'est personnellement satisfaisante. Je suis curieux de savoir quels autres modèles vous avez vus là-bas? Y a-t-il une meilleure façon de le faire?

Un exemple artificiel
Supposons que mon système le soit Alerts. Des alertes peuvent être reçues pour une variété d'objets - Clients, Actualités et Produits. Une alerte donnée peut concerner un et un seul élément. Pour une raison quelconque, les clients, les articles et les produits évoluent rapidement (ou sont localisés), de sorte que le texte / les données nécessaires ne peuvent pas être extraits dans les alertes lors de la création d'une alerte. Compte tenu de cette configuration, j'ai vu deux solutions.

Remarque: ci - dessous DDL est pour SQL Server mais ma question devrait être applicable à n'importe quel SGBD.

Solution 1 - FKeys Nullable multiples

Dans cette solution, la table qui établit un lien vers l'une des nombreuses tables a plusieurs colonnes FK (par souci de concision, le DDL ci-dessous n'affiche pas la création FK). LE BON - Dans cette solution, c'est bien d'avoir des clés étrangères. L'optinalité nulle des FK rend cela pratique et relativement facile pour ajouter des données précises. THE BAD Querying n'est pas génial car il nécessite des instructions N LEFT JOINS ou N UNION pour obtenir les données associées. Dans SQL Server, en particulier les LEFT JOINS empêchent la création d'une vue indexée.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    ProductID   int null,
    NewsID      int null,
    CustomerID  int null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

ALTER TABLE Alert WITH CHECK ADD CONSTRAINT CK_OnlyOneFKAllowed 
CHECK ( 
    (ProductID is not null AND NewsID is     null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is not null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is     null and CustomerID is not null) 
)

Solution 2 - Un FK dans chaque table parent
Dans cette solution, chaque table «parent» a un FK dans la table Alert. Il facilite la récupération des alertes associées à un parent. En revanche, il n'y a pas de véritable chaîne de l'alerte à qui fait référence. En outre, le modèle de données autorise les alertes orphelines - lorsqu'une alerte n'est pas associée à un produit, une actualité ou un client. Encore une fois, plusieurs LEFT JOINs pour comprendre l'association.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

Est-ce juste la vie dans une base de données de relations? Y a-t-il d'autres solutions que vous avez trouvées plus satisfaisantes?

EBarr
la source
1
Pouvez-vous créer une table parent, Alertable, avec les colonnes suivantes: ID, CreateDate, Name, Type. Vous pouvez avoir votre table FK à trois enfants, et votre ALert sera FK à une seule table, Alertable.
AK
Dans certains cas, vous faites valoir un bon argument - la solution n ° 3 est efficace. Cependant, chaque fois que les données se déplacent rapidement ou sont localisées, cela ne fonctionnera pas. Supposons, par exemple, que Product, Customer et News aient chacun un tableau "Lang" correspondant pour prendre en charge Name en plusieurs langues. Si je dois livrer «nom» dans la langue maternelle de l'utilisateur, je ne peux pas le stocker Alertable. Ça a du sens?
EBarr
1
@EBarr: "Si je dois livrer" nom "dans la langue maternelle de l'utilisateur, je ne peux pas le stocker dans Alertable. Cela a-t-il un sens?" Non, non. Avec votre schéma actuel, si vous devez fournir «nom» dans la langue maternelle des utilisateurs, pouvez-vous le stocker dans le tableau Produit, Client ou Actualités?
ypercubeᵀᴹ
@ypercube J'ai ajouté que chacune de ces tables a une table de langue associée. J'essayais de créer un scénario où le texte "nom" peut varier selon la demande et ne peut donc pas être stocké dans Alertable.
EBarr
À moins qu'il n'ait déjà un nom accepté, je propose le terme "octopus join" pour la requête que vous feriez pour voir toutes les alertes et leurs parents associés. :)
Nathan Long

Réponses:

4

Je comprends que la deuxième solution n'est pas applicable car elle n'offre pas de relation un (objet) à plusieurs (alerte).

Vous êtes coincé à seulement deux solutions en raison de la stricte conformité 3NF.

Je voudrais concevoir un schéma de couplage moindre:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  -- See (1)
  -- AlertID     int identity(1,1) not null,

  AlertClass char(1) not null, -- 'P' - Product, 'C' - Customer, 'N' - News
  ForeignKey int not null,
  CreateUTC  datetime2(7) not null,

  -- See (2)
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertClass, ForeignKey)
)

-- (1) you don't need to specify an ID 'just because'. If it's meaningless, just don't.
-- (2) I do believe in composite keys

Ou, si la relation d'intégrité est obligatoire, je pourrais concevoir:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  AlertID     int identity(1,1) not null,
  AlertClass char(1) not null, /* 'P' - Product, 'C' - Customer, 'N' - News */
  CreateUTC  datetime2(7) not null,
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

CREATE TABLE AlertProduct  (AlertID..., ProductID...,  CONSTRAINT FK_AlertProduct_X_Product(ProductID)    REFERENCES Product)
CREATE TABLE AlertCustomer (AlertID..., CustomerID..., CONSTRAINT FK_AlertCustomer_X_Customer(CustomerID) REFERENCES Customer)
CREATE TABLE AlertNews     (AlertID..., NewsID...,     CONSTRAINT FK_AlertNews_X_News(NewsID)             REFERENCES News)

En tous cas...

Trois solutions valides et une autre à considérer pour de nombreuses relations (objets) à un (alerte) ...

Ceux-ci présentés, quelle est la morale?

Ils diffèrent subtilement et pèsent de la même manière sur les critères:

  • performances sur les insertions et les mises à jour
  • complexité de l'interrogation
  • espace de stockage

Alors, choisissez ce confort pour vous.

Marcus Vinicius Pompeu
la source
1
Merci pour la contribution; Je suis d'accord avec la plupart de vos commentaires. J'ai probablement décrit de manière ingénieuse la relation requise (à l'exclusion de la solution 2 à vos yeux), car c'était un exemple inventé. fn1 - compris, je simplifiais beaucoup les tableaux pour me concentrer sur le problème. fn2 - touches composites et je reviens en arrière! Concernant le schéma de couplage moindre, je comprends la simplicité, mais j'essaie personnellement de concevoir avec DRI dans la mesure du possible.
EBarr
En examinant cela, j'ai commencé à douter de l'exactitude de ma solution ... de toute façon, elle a été votée deux fois, ce que je remercie. Bien que je pense que ma conception est valide mais ne convient pas au problème donné, car les 'n' unions / jointures ne sont pas traitées ...
Marcus Vinicius Pompeu
L'acronyme DRI m'a eu. Pour vous tous, cela signifie Declarative Referential Integrity, la technique derrière l'intégrité des données référentielles qui est généralement implémentée sous la forme d'instructions ... (roulement de tambour) ... DDL FOREIGN KEY. Plus d'informations sur en.wikipedia.org/wiki/Declarative_Referential_Integrity et msdn.microsoft.com/en-us/library/…
Marcus Vinicius Pompeu
1

J'ai utilisé des tables de jointure gérées par déclencheur. la solution fonctionne plutôt bien en dernier recours si la refactorisation de la base de données n'est pas possible ou souhaitable. L'idée est que vous avez une table qui est là juste pour gérer les problèmes de RI, et tous les DRI vont à l'encontre.

Chris Travers
la source