Schéma de base de données pour les entités avec deux types de propriétaire / parent possibles?

8

J'utilise PostgreSQL avec Sequelize comme mon ORM.

J'ai un type User,. Le deuxième type est Group, qui peut être associé à n'importe quel nombre d'utilisateurs via une GroupMembershipstable. Users peut également posséder n'importe quel nombre de Groups.

Mon troisième type Playlist,, peut appartenir à un UserOU à un group. Quelle serait la meilleure façon de concevoir un schéma pour ce type afin qu'il puisse avoir un type de propriétaire ou l'un ou l'autre?

Lors de ma première passe, j'ai créé les deux associations, mais je n'en ai rempli qu'une à la fois. Cela pourrait éventuellement fonctionner, mais semble hacky et rend les requêtes difficiles.

Information additionnelle

Voici mes réponses aux demandes de clarification publiées par MDCCL via les commentaires:

(1) Si une liste de lecture appartient à un groupe donné , on peut dire que cette liste de lecture est liée à un à plusieurs utilisateurs , tant qu'ils sont membres de ce groupe , non?

Je crois que c'est techniquement vrai, mais cette association un-à-plusieurs n'existe pas explicitement.

(2) Alors, est-il possible qu'une Playlist spécifique soit détenue par des groupes un-à-plusieurs en même temps?

Non, il ne devrait pas être possible pour un Playlistd'appartenir à un à plusieurs Groups.

(3) Est - il possible pour un particulier Playlist à appartenir à un à plusieurs groupes et, en même temps, par un à de nombreux utilisateurs qui ne sont pas membres de ce groupe ?

Non, car comme dans (2), un à plusieurs de Playlistà Groupne devrait pas exister. De plus, si a Playlistappartient à a, Groupil n'appartient pas à a Useret vice versa. Un seul propriétaire à la fois.

(4) Quelles sont les propriétés utilisées pour identifier de manière unique un groupe , un utilisateur et une liste de lecture ?

Ils ont chacun une clé primaire de substitution ( id), ainsi qu'une clé naturelle (mais pas primaire). Ce sont slugpour Groupet Playlist, et usernamepour User.

(5) Une liste de lecture particulière pourrait-elle subir un changement de propriétaire ?

Bien que je ne prévoie pas que ce soit une fonctionnalité (au moins initialement), je suppose que cela pourrait se produire hypothétiquement.

(6) Quelle est la signification des attributs Group.Slug et Playlist.Slug ? Leurs valeurs sont-elles suffisamment stables pour être définies comme clés primaires ou changent-elles très souvent? Les valeurs de ces deux propriétés, ainsi que User.Username doivent être uniques, correct?

Ces slugs sont des versions uniques, en minuscules, avec trait d'union de leurs entités respectives title. Par exemple, un groupavec le title'Test Group' aurait le slug'test-group'. Les doublons sont ajoutés avec des entiers incrémentiels. Cela changerait à tout moment leurs titlechangements. Je crois que cela signifie qu'ils ne feraient pas de bonnes clés primaires? Oui, slugset usernamessont uniques dans leurs tableaux respectifs.

Jakemmarsh
la source

Réponses:

9

Si je comprends bien vos spécifications, votre scénario implique - parmi d'autres aspects importants - une structure supertype-sous-type .

Je vais illustrer ci-dessous comment (1) le modéliser au niveau conceptuel de l'abstraction et (2) le représenter par la suite dans une conception DDL au niveau logique .

Règles métier

Les formulations conceptuelles suivantes sont parmi les règles les plus importantes dans votre contexte commercial:

  • Une liste de lecture appartient à exactement un groupe ou exactement un utilisateur à un moment donné
  • Une liste de lecture peut appartenir à un ou plusieurs groupes ou utilisateurs à des moments différents
  • Un utilisateur possède zéro ou une ou plusieurs listes de lecture
  • Un groupe possède zéro ou une ou plusieurs listes de lecture
  • Un groupe est composé d'un à plusieurs membres (qui doivent être des utilisateurs )
  • Un utilisateur peut être membre de groupes zéro-un ou plusieurs .
  • Un groupe est composé d'un à plusieurs membres (qui doivent être des utilisateurs )

Étant donné que les associations ou relations (a) entre l' utilisateur et la liste de lecture et (b) entre le groupe et la liste de lecture sont assez similaires, ce fait révèle que l' utilisateur et le groupe sont des sous-types d'entités mutuellement exclusifs de la Partie 1 , qui est à son tour leur supertype d'entité —supertype- les clusters de sous-types sont des structures de données classiques qui apparaissent dans des schémas conceptuels de types très divers. De cette manière, deux nouvelles règles peuvent être affirmées:

  • Une Party est catégorisée par exactement un PartyType
  • Une partie est un groupe ou un utilisateur

Et quatre des règles précédentes doivent être reformulées en seulement trois:

  • Une liste de lecture appartient à une seule partie à un moment précis
  • Une liste de lecture peut appartenir à une ou plusieurs parties à des moments différents
  • Une partie possède zéro ou une ou plusieurs listes de lecture

Diagramme expositoire IDEF1X

Le diagramme IDEF1X 2 illustré à la figure 1 consolide toutes les règles métier susmentionnées ainsi que d'autres qui semblent pertinentes:

Figure 1 - Diagramme IDEF1X des propriétaires de listes de lecture

Comme démontré, le groupe et l' utilisateur sont représentés comme des sous-types qui sont connectés par les lignes respectives et le symbole exclusif avec Party , le supertype.

La propriété Party.PartyTypeCode représente le discriminateur de sous-type , c'est-à-dire qu'elle indique quel type d'instance de sous-type doit compléter une occurrence de supertype donnée.

De plus, Party est connecté à Playlist via la propriété OwnerId qui est représentée comme une CLÉ ÉTRANGÈRE qui pointe vers Party.PartyId . De cette manière, le Parti met en corrélation (a) la Playlist avec (b) le Groupe et (c) l' Utilisateur .

En conséquence, étant donné qu'une instance de partie particulière est soit un groupe soit un utilisateur , une liste de lecture spécifique peut être liée à au plus une occurrence de sous-type.

Présentation illustrative au niveau logique

Le diagramme IDEF1X exposé précédemment m'a servi de plate-forme pour créer l'arrangement SQL-DDL logique suivant (et j'ai fourni des notes sous forme de commentaires mettant en évidence plusieurs points particulièrement pertinents - par exemple, les déclarations de contraintes -):

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business domain.

-- As one would expect, you are free to utilize 
-- your preferred (or required) naming conventions. 

CREATE TABLE PartyType ( -- Represents an independent entity type.
    PartyTypeCode CHAR(1)  NOT NULL,
    Name          CHAR(30) NOT NULL,  
    --
    CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode),
    CONSTRAINT PartyType_AK UNIQUE      (Name)  
);

CREATE TABLE Party ( -- Stands for the supertype.
    PartyId         INT       NOT NULL,
    PartyTypeCode   CHAR(1)   NOT NULL, -- Symbolizes the discriminator.
    CreatedDateTime TIMESTAMP NOT NULL,  
    --
    CONSTRAINT Party_PK            PRIMARY KEY (PartyId),
    CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode)
        REFERENCES PartyType (PartyTypeCode)
);

CREATE TABLE UserProfile ( -- Denotes one of the subtypes. 
    UserId     INT      NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    UserName   CHAR(30) NOT NULL,  
    FirstName  CHAR(30) NOT NULL,
    LastName   CHAR(30) NOT NULL,
    GenderCode CHAR(3)  NOT NULL,
    BirthDate  DATE     NOT NULL,
    --
    CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK1 UNIQUE ( -- Multi-column ALTERNATE KEY.
        FirstName,
        LastName,
        GenderCode,
        BirthDate
    ),
    CONSTRAINT UserProfile_AK2       UNIQUE (UserName), -- Single-column ALTERNATE KEY.
    CONSTRAINT UserProfileToParty_FK FOREIGN KEY (UserId)
        REFERENCES Party (PartyId)
);

CREATE TABLE MyGroup ( -- Represents the other subtype.
    GroupId INT      NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
    Title   CHAR(30) NOT NULL,
    --
    CONSTRAINT Group_PK        PRIMARY KEY (GroupId),
    CONSTRAINT Group_AK        UNIQUE      (Title), -- ALTERNATE KEY.
    CONSTRAINT GroupToParty_FK FOREIGN KEY (GroupId)
        REFERENCES Party (PartyId)
);

CREATE TABLE Playlist ( -- Stands for an independent entity type.
    PlaylistId      INT       NOT NULL,
    OwnerId         INT       NOT NULL,  
    Title           CHAR(30)  NOT NULL,
    CreatedDateTime TIMESTAMP NOT NULL,  
    --
    CONSTRAINT Playlist_PK     PRIMARY KEY (PlaylistId),
    CONSTRAINT Playlist_AK     UNIQUE      (Title),  -- ALTERNATE KEY.
    CONSTRAINT PartyToParty_FK FOREIGN KEY (OwnerId) -- Establishes the relationship with (a) the supertype and (b) through the subtype with (c) the subtypes.
        REFERENCES Party (PartyId)
);

CREATE TABLE GroupMember ( -- Denotes an associative entity type.
    MemberId       INT       NOT NULL, 
    GroupId        INT       NOT NULL,
    IsOwner        BOOLEAN   NOT NULL,    
    JoinedDateTime TIMESTAMP NOT NULL,
    --        
    CONSTRAINT GroupMember_PK              PRIMARY KEY (MemberId, GroupId), -- Composite PRIMARY KEY.
    CONSTRAINT GroupMemberToUserProfile_FK FOREIGN KEY (MemberId)
        REFERENCES UserProfile (UserId),
    CONSTRAINT GroupMemberToMyGroup_FK     FOREIGN KEY (GroupId)
        REFERENCES MyGroup (GroupId)  
);

Bien sûr, vous pouvez effectuer un ou plusieurs ajustements afin que toutes les caractéristiques de votre contexte commercial soient représentées avec la précision requise dans la base de données réelle.

Remarque : J'ai testé la disposition logique ci-dessus sur ce violon db <> et également sur ce SQL Fiddle , tous deux «en cours d'exécution» sur PostgreSQL 9.6, afin que vous puissiez les voir «en action».

Les limaces

Comme vous pouvez le voir, je n'ai pas inclus Group.Slugni Playlist.Slugcolonnes dans les déclarations DDL. Il en est ainsi parce que, en accord avec votre explication suivante

Ces slugs sont des versions uniques, en minuscules, avec trait d'union de leurs entités respectives title. Par exemple, un groupavec le title'Test Group' aurait le slug'test-group'. Les doublons sont ajoutés avec des entiers incrémentiels. Cela changerait à tout moment leurs titlechangements. Je crois que cela signifie qu'ils ne feraient pas de bonnes clés primaires? Oui, slugset usernamessont uniques dans leurs tableaux respectifs.

on peut conclure que leurs valeurs sont dérivables (c'est-à-dire qu'elles doivent être calculées ou calculées en fonction des valeurs correspondantes Group.Titleet Playlist.Title, parfois en conjonction avec — je suppose, une sorte de système généré — INTEGER), donc je ne déclarerais pas lesdites colonnes dans n'importe laquelle des tables de base car elles introduiraient des irrégularités de mise à jour.

En revanche, je produirais le Slugs

  • peut-être, dans une vue , qui (a) inclut la dérivation de ces valeurs dans des colonnes virtuelles et (b) peut être utilisée directement dans d'autres opérations SELECT - l'ajout de la partie INTEGER pourrait être obtenu, par exemple, en combinant la valeur de (1) le Playlist.OwnerIdavec (2) les tirets intermédiaires et (3) la valeur du Playlist.Title;

  • ou, en vertu du code du programme d'application, imitant l'approche décrite précédemment (peut-être de manière procédurale), une fois que les ensembles de données pertinents sont récupérés et formatés pour une interprétation par l'utilisateur final.

De cette manière, l' une de ces deux méthodes éviterait le mécanisme « de synchronisation de mise à jour » qui devrait être mis en place ssi le Slugssont conservés dans les colonnes de tables de base.

Considérations d'intégrité et de cohérence

Il est essentiel de mentionner que (i) chaque Party ligne doit être complétée à tout moment par (ii) la contrepartie respective dans exactement l'un des tableaux représentant les sous-types, qui (iii) doit «respecter» la valeur contenue dans la Party.PartyTypeCodecolonne —Indiquant le discriminateur—.

Il serait tout à fait avantageux d'appliquer ce type de situation de manière déclarative , mais aucun des principaux systèmes de gestion de base de données SQL (y compris Postgres) n'a fourni les instruments nécessaires pour procéder de la sorte; par conséquent, l'écriture de code procédural dans ACID TRANSACTIONS est jusqu'à présent la meilleure option pour garantir que les circonstances décrites précédemment sont toujours respectées dans votre base de données. Une autre possibilité serait de recourir à des déclencheurs, mais ils sont enclins à rendre les choses désordonnées, pour ainsi dire.

Cas comparables

Si vous souhaitez établir des analogies, vous pourriez être intéressé à jeter un œil à mes réponses aux (nouvelles) questions intitulées

puisque des scénarios comparables sont discutés.


Notes de fin

1 Partie est un terme utilisé dans des contextes juridiques pour désigner un individu ou un groupe d'individus qui composent une seule entité , de sorte que cette dénomination convient pour représenter les concepts d' utilisateur et de groupe par rapport à l'environnement commercial en question.

2 La définition d'intégration pour la modélisation de l'information ( IDEF1X ) est une technique de modélisation de données hautement recommandable qui a été établie comme norme en décembre 1993 par le National Institute of Standards and Technology (NIST)des États-Unis. Il est solidement basé sur (a) certains des premiers travaux théoriques rédigés par le seul auteur du modèle relationnel , à savoir le Dr EF Codd ; sur (b) la vue entité-relation , développée par le Dr PP Chen ; et également sur (c) la technique de conception de bases de données logiques, créée par Robert G. Brown.

MDCCL
la source
je ne vois aucune contrainte pour la fête est exactement celle de l'utilisateur ou du groupe
Jasen
Il semble que votre commentaire concerne les extraits de questions suivants (tous deux contenus dans la section intitulée «Considérations relatives à l'intégrité et à la cohérence»):
MDCCL
« Il est essentiel de mentionner que (i) chaque Partyligne doit être complétée à tout moment par (ii) la contrepartie respective dans exactement l' un des tableaux représentant les sous-types, qui (iii) doit« respecter »la valeur contenue dans le Party.PartyTypeCodecolonne "indiquant le discriminateur ".
MDCCL
« Il serait tout à fait avantageux d'appliquer ce type de situation de manière déclarative , mais aucun des principaux systèmes de gestion de bases de données SQL (y compris Postgres) n'a fourni les instruments nécessaires pour procéder ainsi; par conséquent, l'écriture de code procédural dans ACID TRANSACTIONS est jusqu'à présent la meilleure option pour garantir que les circonstances décrites précédemment sont toujours respectées dans votre base de données. Une autre possibilité serait de recourir aux DÉCLENCHEURS, mais ils sont enclins à rendre les choses désordonnées, pour ainsi dire ».
MDCCL
Par conséquent, une fois (j'espère bientôt) Postgres fournit, disons, des ASSERTIONS, je serais heureux d'inclure les contraintes (déclaratives) applicables. Pour le moment, les deux approches disponibles fournies par SGBD sont suggérées ci-dessus.
MDCCL