Comment modélisez-vous efficacement l'héritage dans une base de données?


131

Quelles sont les meilleures pratiques pour modéliser l'héritage dans les bases de données?

Quels sont les compromis (par exemple, queriability)?

(Je suis particulièrement intéressé par SQL Server et .NET, mais je souhaite également comprendre comment d'autres plates-formes résolvent ce problème.)


14
Si vous êtes intéressé par les «meilleures pratiques», la plupart des réponses sont tout simplement incorrectes. Les meilleures pratiques dictent que la RDb et l'application sont indépendantes; ils ont des critères de conception complètement différents. Par conséquent, «modéliser l'héritage» dans une base de données (ou modéliser la RDb pour l'adapter à une seule application ou à un langage d'application) est une très mauvaise pratique, non informée, et enfreint les règles de conception de base de RDb et la paralyse.
PerformanceDBA


6
@PerformanceDBA Alors, quelle est votre suggestion pour éviter l'héritage dans le modèle DB? Disons que nous avons 50 types d'enseignants différents et que nous voulons connecter cet enseignant particulier à la classe. Comment y parvenir sans héritage?
svlada

1
@svlada. C'est simple à implémenter dans un RDb, donc "héritage" requis. Posez une question, incluez les définitions de tableau et un exemple, et je vais y répondre en détail. Si vous le faites en termes OO, ce sera un gâchis royal.
PerformanceDBA

Réponses:


162

Il existe plusieurs façons de modéliser l'héritage dans une base de données. Ce que vous choisissez dépend de vos besoins. Voici quelques options:

Table par type (TPT)

Chaque classe a sa propre table. La classe de base contient tous les éléments de la classe de base, et chaque classe qui en dérive a sa propre table, avec une clé primaire qui est également une clé étrangère de la table de classe de base; la classe de la table dérivée contient uniquement les différents éléments.

Donc par exemple:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

Cela donnerait des tableaux comme:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

Table par hiérarchie (TPH)

Il existe une table unique qui représente toute la hiérarchie d'héritage, ce qui signifie que plusieurs colonnes seront probablement rares. Une colonne discriminante est ajoutée qui indique au système de quel type de ligne il s'agit.

Compte tenu des classes ci-dessus, vous vous retrouvez avec ce tableau:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

Pour toutes les lignes de type 0 (Person), la date de début sera toujours nulle.

Table par béton (TPC)

Chaque classe a sa propre table entièrement formée sans aucune référence à d'autres tables.

Compte tenu des classes ci-dessus, vous vous retrouvez avec ces tables:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate

23
«Ce que vous choisissez dépend de vos besoins» - veuillez préciser, car je pense que les raisons des choix sont au cœur de la question.
Alex

12
Voir mon commentaire sur la question. L'utilisation de nouveaux noms amusants pour les termes techniques Rdb qui ont existé prête à confusion. «TPT» est un sous-type de supertype. "TPH" est anormal, une erreur grossière. "TPH" est encore moins normalisé, une autre erreur grossière.
PerformanceDBA

45
Seul un DBA présumerait que la dénormalisation est toujours une erreur. :)
Brad Wilson

7
Bien que je concède que la dénormalisation entraîne des gains de performances dans certains cas, cela est entièrement dû à une séparation incomplète (ou inexistante) entre la structure logique et physique des données dans le SGBD. Malheureusement, la majorité des SGBD commerciaux souffrent de ce problème. @PerformanceDBA est correct. La sous-normalisation est une erreur de jugement, sacrifiant la cohérence des données au profit de la vitesse. Malheureusement, c'est un choix qu'un DBA ou un développeur n'aurait jamais besoin de faire si le SGBD était correctement conçu. Pour mémoire, je ne suis pas un DBA.
Kenneth Cochran

6
@Brad Wilson. Seul un développeur dénormaliserait, «pour la performance», ou autrement. Souvent, ce n'est pas une dénormalisation, la vérité est qu'elle n'est pas normalisée. Que la dénormalisation ou non normalisée est une erreur, est un fait, soutenu par la théorie, et vécu par des millions de personnes, ce n'est pas une «présomption».
PerformanceDBA

133

Une bonne conception de base de données n'a rien à voir avec une conception d'objet appropriée.

Si vous envisagez d'utiliser la base de données pour autre chose que la simple sérialisation de vos objets (tels que des rapports, des requêtes, une utilisation multi-applications, une veille stratégique, etc.), je ne recommande aucun type de simple mappage d'objets vers des tables.

Beaucoup de gens considèrent une ligne dans une table de base de données comme une entité (j'ai passé de nombreuses années à réfléchir en ces termes), mais une ligne n'est pas une entité. C'est une proposition. Une relation de base de données (c.-à-d., Table) représente un énoncé de fait sur le monde. La présence de la ligne indique que le fait est vrai (et inversement, son absence indique que le fait est faux).

Avec cette compréhension, vous pouvez voir qu'un seul type dans un programme orienté objet peut être stocké dans une douzaine de relations différentes. Et une variété de types (unis par héritage, association, agrégation ou complètement non affiliés) peuvent être partiellement stockés dans une seule relation.

Il est préférable de vous demander quels faits voulez-vous stocker, à quelles questions voulez-vous des réponses, quels rapports voulez-vous générer.

Une fois la conception de base de données appropriée créée, il suffit de créer des requêtes / vues qui vous permettent de sérialiser vos objets dans ces relations.

Exemple:

Dans un système de réservation d'hôtel, vous devrez peut-être mémoriser le fait que Jane Doe a réservé une chambre au Seaview Inn du 10 au 12 avril. Est-ce un attribut de l'entité cliente? Est-ce un attribut de l'entité hôtelière? S'agit-il d'une entité de réservation avec des propriétés qui incluent le client et l'hôtel? Il peut s'agir de tout ou partie de ces éléments dans un système orienté objet. Dans une base de données, ce n'est rien de tout cela. C'est simplement un simple fait.

Pour voir la différence, considérez les deux requêtes suivantes. (1) Combien de réservations d'hôtel Jane Doe a-t-elle pour l'année prochaine? (2) Combien de chambres sont réservées pour le 10 avril au Seaview Inn?

Dans un système orienté objet, la requête (1) est un attribut de l'entité client et la requête (2) est un attribut de l'entité hôtelière. Ce sont les objets qui exposeraient ces propriétés dans leurs API. (Cependant, de toute évidence, les mécanismes internes par lesquels ces valeurs sont obtenues peuvent impliquer des références à d'autres objets.)

Dans un système de base de données relationnelle, les deux requêtes examineraient la relation de réservation pour obtenir leurs numéros et, conceptuellement, il n'est pas nécessaire de s'embêter avec une autre «entité».

C'est donc en essayant de stocker des faits sur le monde - plutôt qu'en essayant de stocker des entités avec des attributs - qu'une base de données relationnelle appropriée est construite. Et une fois qu'elle est correctement conçue, les requêtes utiles qui n'étaient pas envisagées pendant la phase de conception peuvent être facilement construites, car tous les faits nécessaires pour répondre à ces requêtes sont à leur place.


12
+1 Enfin, un îlot de connaissances authentiques dans une mer d'ignorance (et le refus d'apprendre quoi que ce soit en dehors de leur domaine). D'accord, ce n'est pas magique: si le RDb est conçu en utilisant des principes RDb, il est facile de "mapper" ou de "projeter" n'importe quelle "classe". Forcer le RDb dans des exigences basées sur les classes est tout simplement incorrect.
PerformanceDBA

2
Réponse intéressante. Comment suggéreriez-vous de modéliser l'exemple Personne-Employé dans la réponse acceptée?
sevenforce

2
@ sevenforce-La conception de la base de données dépend vraiment des exigences du système, qui ne sont pas données. Il n'y a pas assez d'informations fournies pour décider. Dans de nombreux cas, quelque chose de similaire à la conception "table par type" peut être approprié, sinon suivi servilement. Par exemple, la date de début est probablement une bonne propriété pour un objet Employé, mais dans la base de données, elle devrait vraiment être un champ dans la table Emploi, car une personne peut être embauchée plusieurs fois avec plusieurs dates de début. Cela n'a pas d'importance pour les objets (qui utiliseraient le plus récent), mais c'est important dans la base de données.
Jeffrey L Whitledge

2
Bien sûr, ma question portait principalement sur la manière de modéliser l'héritage. Désolé de ne pas avoir été assez clair. Merci. Comme vous l'avez mentionné, il devrait probablement y avoir un Employmenttableau qui recueille tous les emplois avec leurs dates de début. Donc, s'il Employerest important de connaître la date de début d'emploi actuelle d'un an , cela pourrait être un cas d'utilisation approprié pour a View, qui inclut cette propriété en interrogeant? (note: il semble
qu'à

5
C'est un vrai bijou de réponse. Il faudra un peu de temps pour vraiment s'imprégner et un peu d'exercice pour bien faire, mais cela a déjà influencé mon processus de réflexion sur la conception de bases de données relationnelles.
MarioDS

9

Réponse courte: vous ne le faites pas.

Si vous avez besoin de sérialiser vos objets, utilisez un ORM, ou mieux quelque chose comme activerecord ou prevaylence.

Si vous avez besoin de stocker des données, stockez-les de manière relationnelle (en faisant attention à ce que vous stockez et en faisant attention à ce que Jeffrey L. Whitledge vient de dire), pas affectée par la conception de votre objet.


3
+1 Tenter de modéliser l'héritage dans une base de données est un gaspillage de bonnes ressources relationnelles.
Daniel Spiewak

7

Les modèles TPT, TPH et TPC sont la voie à suivre, comme mentionné par Brad Wilson. Mais quelques notes:

  • les classes enfants héritant d'une classe de base peuvent être considérées comme des entités faibles par rapport à la définition de classe de base dans la base de données, ce qui signifie qu'elles dépendent de leur classe de base et ne peuvent exister sans elle. J'ai vu un certain nombre de fois que des identifiants uniques sont stockés pour chaque table enfant tout en conservant le FK dans la table parent. Un FK est juste suffisant et il est encore mieux d'avoir une activation en cascade lors de la suppression pour la relation FK entre les tables enfant et de base.

  • Dans TPT, en ne voyant que les enregistrements de la table de base, vous ne pouvez pas trouver la classe enfant représentée par l'enregistrement. Cela est parfois nécessaire, lorsque vous souhaitez charger une liste de tous les enregistrements (sans faire select sur chaque table enfant). Une façon de gérer cela est d'avoir une colonne représentant le type de la classe enfant (similaire au champ rowType dans le TPH), mélangeant ainsi en quelque sorte le TPT et le TPH.

Supposons que nous souhaitons concevoir une base de données contenant le diagramme de classes de formes suivant:

public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}

public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}

public class Circle : Shape {
Point center;
int radius;
}

La conception de la base de données pour les classes ci-dessus peut être comme ceci:

table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)

table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;

table Circle
----------
int ShapeID; (FK on delete cascade)  
int centerX;
int center;
int radius;

4

Il existe deux principaux types d'héritage que vous pouvez configurer dans une base de données, une table par entité et une table par hiérarchie.

La table par entité est l'endroit où vous avez une table d'entité de base qui a les propriétés partagées de toutes les classes enfants. Vous avez ensuite par classe enfant une autre table, chacune avec uniquement des propriétés applicables à cette classe. Ils sont liés 1: 1 par leurs PK

texte alternatif

La table par hiérarchie est l'endroit où toutes les classes ont partagé une table et les propriétés facultatives peuvent être nulles. Leur est également un champ discriminateur qui est un nombre qui indique le type que l'enregistrement détient actuellement

texte alternatif SessionTypeID est un discriminateur

La cible par hiérarchie est plus rapide à interroger car vous n'avez pas besoin de jointures (uniquement la valeur du discriminateur), tandis que la cible par entité, vous devez effectuer des jointures complexes afin de détecter le type de quelque chose et de récupérer toutes ses données.

Edit: Les images que je montre ici sont des captures d'écran d'un projet sur lequel je travaille. L'image Asset n'est pas complète, d'où le vide de celle-ci, mais c'était surtout pour montrer comment sa configuration, pas quoi mettre à l'intérieur de vos tables. Cela dépend de toi ;). La table de session contient des informations sur la session de collaboration virtuelle et peut être de plusieurs types de sessions selon le type de collaboration impliqué.


Je considérerais également Target par classe Concrete pour ne pas vraiment modéliser bien l'héritage et je n'ai donc pas montré.
mattlant

Pourriez-vous ajouter une référence d'où provient l'illustration?
chryss

Où sont les images dont vous parlez à la fin de votre réponse?
Musa Haidari

1

Vous normaliseriez votre base de données et cela refléterait en fait votre héritage. Cela peut avoir une dégradation des performances, mais c'est comme ça avec la normalisation. Vous devrez probablement faire preuve de bon sens pour trouver l'équilibre.


2
pourquoi les gens pensent-ils que la normalisation d'une base de données dégrade les performances? les gens pensent-ils également que le principe DRY dégrade les performances du code? d'où vient cette fausse perception?
Steven A. Lowe

1
Peut-être parce que la dénormalisation peut améliorer les performances, donc la normalisation la dégrade, relativement parlant. Je ne peux pas dire que je suis d'accord, mais c'est probablement ainsi que cela s'est produit.
Matthew Scharley

2
Au début, la normalisation peut avoir un petit effet sur les performances, mais avec le temps, à mesure que le nombre de lignes augmente, les JOINs efficaces commenceront à surpasser les tables plus volumineuses. Bien sûr, la normalisation a d'autres avantages plus importants - cohérence et manque de redondance, etc.
Rob

1

répétition d'une réponse similaire

dans le mappage OR, l'héritage est mappé à une table parent où les tables parent et enfant utilisent le même identifiant

par exemple

create table Object (
    Id int NOT NULL --primary key, auto-increment
    Name varchar(32)
)
create table SubObject (
    Id int NOT NULL  --primary key and also foreign key to Object
    Description varchar(32)
)

SubObject a une relation de clé étrangère avec Object. lorsque vous créez une ligne de sous-objet, vous devez d'abord créer une ligne d'objet et utiliser l'ID dans les deux lignes

EDIT: si vous cherchez à modéliser également le comportement, vous aurez besoin d'une table Type qui répertorie les relations d'héritage entre les tables et spécifie l'assembly et le nom de classe qui implémentent le comportement de chaque table

Cela semble exagéré, mais tout dépend de l'utilisation que vous en faites!


Cette discussion a fini par porter sur l'ajout de quelques colonnes à chaque table, et non sur la modélisation de l'héritage. Je pense que le titre de cette discussion devrait être changé pour mieux refléter la nature de la question et de la discussion.
Even Mien

1

En utilisant SQL ALchemy (Python ORM), vous pouvez effectuer deux types d'héritage.

Celle que j'ai eue utilise une table unique et une colonne discriminante. Par exemple, une base de données Sheep (sans blague!) Stockait tous les moutons dans une seule table, et les béliers et les brebis étaient traités en utilisant une colonne de genre dans cette table.

Ainsi, vous pouvez rechercher tous les moutons et obtenir tous les moutons. Ou vous pouvez interroger uniquement par Ram, et il n'obtiendra que des Rams. Vous pouvez également faire des choses comme avoir une relation qui ne peut être qu'un bélier (c'est-à-dire, le père d'un mouton), etc.


1

Notez que certains moteurs de base de données fournissent déjà des mécanismes d'héritage nativement comme Postgres . Regardez la documentation .

Pour un exemple, vous interrogez le système Personne / Employé décrit dans une réponse ci-dessus comme ceci:

  / * Ceci montre le prénom de toutes les personnes ou employés * /
  SELECT prenom FROM Personne; 

  / * Affiche la date de début de tous les employés uniquement * /
  SELECT startdate FROM Employee;

Dans ce choix de votre base de données, vous n'avez pas besoin d'être particulièrement intelligent!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.