En analysant le scénario - qui présente des caractéristiques associées au sujet connu sous le nom de bases de données temporelles - d'un point de vue conceptuel, on peut déterminer que: (a) une version «actuelle» du blog et (b) une version «passée» du blog , bien que très ressemblant, sont des entités de différents types.
De plus, lorsque vous travaillez au niveau logique de l'abstraction, les faits (représentés par des lignes) de types différents doivent être conservés dans des tableaux distincts. Dans le cas considéré, même lorsqu'ils sont assez similaires, (i) les faits concernant les versions «actuelles» sont différents des (ii) faits concernant les versions «passées» .
Par conséquent, je recommande de gérer la situation au moyen de deux tableaux:
une dédiée exclusivement aux versions «actuelles» ou «actuelles» des histoires du blog , et
une qui est distincte, mais également liée à l'autre, pour toutes les versions «précédentes» ou «passées» ;
chacun avec (1) un nombre légèrement distinct de colonnes et (2) un groupe différent de contraintes.
De retour à la couche conceptuelle, je considère que - dans votre environnement professionnel - Auteur et éditeur sont des notions qui peuvent être définies comme des rôles pouvant être joués par un utilisateur , et ces aspects importants dépendent de la dérivation des données (via des opérations de manipulation au niveau logique) et interprétation (réalisée par les lecteurs et rédacteurs de Blog Stories , au niveau externe du système d'information informatisé, avec l'aide d'un ou plusieurs programmes d'application).
Je détaillerai tous ces facteurs et d'autres points pertinents comme suit.
Règles métier
Selon ma compréhension de vos besoins, les formulations de règles commerciales suivantes (regroupées en termes de types d'entités pertinents et de leurs types d'interrelations) sont particulièrement utiles pour établir le schéma conceptuel correspondant :
- Un utilisateur écrit zéro ou un ou plusieurs BlogStories
- Un BlogStory contient zéro ou un ou plusieurs BlogStoryVersions
- Un utilisateur a écrit zéro ou un ou plusieurs BlogStoryVersions
Diagramme expositoire IDEF1X
Par conséquent, afin d'exposer ma suggestion grâce à un dispositif graphique, j'ai créé un exemple d'IDEF1X, un diagramme dérivé des règles métier formulées ci-dessus et d'autres fonctionnalités qui semblent pertinentes. Il est illustré à la figure 1 :
Pourquoi BlogStory et BlogStoryVersion sont - ils conceptualisés comme deux types d'entités différents?
Car:
Une instance BlogStoryVersion (c'est-à-dire une «ancienne») contient toujours une valeur pour une propriété UpdatedDateTime , tandis qu'une occurrence BlogStory (c'est-à-dire une «présente») ne la contient jamais.
En outre, les entités de ces types sont identifiées de manière unique par les valeurs de deux ensembles de propriétés distincts: BlogStoryNumber (dans le cas des occurrences BlogStory ) et BlogStoryNumber plus CreatedDateTime (dans le cas des instances BlogStoryVersion ).
Une 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 en tant que norme en décembre 1993 par le National Institute of Standards and Technology (NIST)des États-Unis. Il est basé sur les premiers éléments théoriques rédigés par le seul auteur du modèle relationnel , c'est-à-dire le Dr EF Codd ; sur la vue Entité-Relation des données, développée par le Dr PP Chen ; et également sur la technique de conception de bases de données logiques, créée par Robert G. Brown.
Exemple de disposition logique SQL-DDL
Ensuite, sur la base de l'analyse conceptuelle présentée précédemment, j'ai déclaré la conception au niveau logique ci-dessous:
-- 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 at the physical level.
-- As one would expect, you are free to make use of
-- your preferred (or required) naming conventions.
CREATE TABLE UserProfile (
UserId INT NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
BirthDate DATETIME NOT NULL,
GenderCode CHAR(3) NOT NULL,
UserName CHAR(20) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
FirstName,
LastName,
BirthDate,
GenderCode
),
CONSTRAINT UserProfile_AK2 UNIQUE (UserName) -- ALTERNATE KEY.
);
CREATE TABLE BlogStory (
BlogStoryNumber INT NOT NULL,
Title CHAR(60) NOT NULL,
Content TEXT NOT NULL,
CoverImageName CHAR(30) NOT NULL,
IsActive BIT(1) NOT NULL,
AuthorId INT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT BlogStory_PK PRIMARY KEY (BlogStoryNumber),
CONSTRAINT BlogStory_AK UNIQUE (Title), -- ALTERNATE KEY.
CONSTRAINT BlogStoryToUserProfile_FK FOREIGN KEY (AuthorId)
REFERENCES UserProfile (UserId)
);
CREATE TABLE BlogStoryVersion (
BlogStoryNumber INT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
Title CHAR(60) NOT NULL,
Content TEXT NOT NULL,
CoverImageName CHAR(30) NOT NULL,
IsActive BIT(1) NOT NULL,
AuthorId INT NOT NULL,
UpdatedDateTime DATETIME NOT NULL,
--
CONSTRAINT BlogStoryVersion_PK PRIMARY KEY (BlogStoryNumber, CreatedDateTime), -- Composite PK.
CONSTRAINT BlogStoryVersionToBlogStory_FK FOREIGN KEY (BlogStoryNumber)
REFERENCES BlogStory (BlogStoryNumber),
CONSTRAINT BlogStoryVersionToUserProfile_FK FOREIGN KEY (AuthorId)
REFERENCES UserProfile (UserId),
CONSTRAINT DatesSuccession_CK CHECK (UpdatedDateTime > CreatedDateTime) --Let us hope that MySQL will finally enforce CHECK constraints in a near future version.
);
Testé dans ce SQL Fiddle qui fonctionne sur MySQL 5.6.
La BlogStory
table
Comme vous pouvez le voir dans la conception de la démonstration, j'ai défini la BlogStory
colonne PRIMARY KEY (PK pour la brièveté) avec le type de données INT. À cet égard, vous pouvez souhaiter corriger un processus automatique intégré qui génère et attribue une valeur numérique pour une telle colonne à chaque insertion de ligne. Si cela ne vous dérange pas de laisser occasionnellement des lacunes dans cet ensemble de valeurs, vous pouvez utiliser l' attribut AUTO_INCREMENT , couramment utilisé dans les environnements MySQL.
Lorsque vous entrez tous vos BlogStory.CreatedDateTime
points de données individuels , vous pouvez utiliser la fonction NOW () , qui renvoie les valeurs de date et d'heure qui sont actuelles dans le serveur de base de données à l'instant exact de l'opération INSERT. Pour moi, cette pratique est décidément plus adaptée et moins sujette aux erreurs que l'utilisation de routines externes.
À condition que, comme indiqué dans les commentaires (désormais supprimés), vous souhaitiez éviter la possibilité de conserver BlogStory.Title
des valeurs en double, vous devez configurer une contrainte UNIQUE pour cette colonne. En raison du fait qu'un donné titre peut être partagée par plusieurs (voire tous les) « passé » BlogStoryVersions , alors une contrainte UNIQUE doit pas être établie pour la BlogStoryVersion.Title
colonne.
J'ai inclus la BlogStory.IsActive
colonne de type BIT (1) (même si un TINYINT peut aussi bien être utilisé) au cas où vous auriez besoin de fournir une fonctionnalité de suppression «douce» ou «logique».
Détails sur la BlogStoryVersion
table
D'autre part, le PK de la BlogStoryVersion
table est composé de (a) BlogStoryNumber
et (b) une colonne nommée CreatedDateTime
qui, bien sûr, marque l'instant précis où une BlogStory
ligne a subi un INSERT.
BlogStoryVersion.BlogStoryNumber
, en plus de faire partie du PK, est également contraint en tant que CLÉ ÉTRANGÈRE (FK) qui fait référence BlogStory.BlogStoryNumber
, une configuration qui applique l' intégrité référentielle entre les lignes de ces deux tables. À cet égard, la mise en œuvre d'une génération automatique de a BlogStoryVersion.BlogStoryNumber
n'est pas nécessaire car, étant définie comme un FK, les valeurs INSÉRÉES dans cette colonne doivent être «tirées» de celles déjà incluses dans la BlogStory.BlogStoryNumber
contrepartie associée .
La BlogStoryVersion.UpdatedDateTime
colonne doit conserver, comme prévu, le moment où une BlogStory
ligne a été modifiée et, par conséquent, ajoutée au BlogStoryVersion
tableau. Par conséquent, vous pouvez également utiliser la fonction NOW () dans cette situation.
L' intervalle compris entre BlogStoryVersion.CreatedDateTime
et BlogStoryVersion.UpdatedDateTime
exprime toute la période pendant laquelle une BlogStory
ligne était «présente» ou «actuelle».
Considérations pour une Version
colonne
Il peut être utile de penser BlogStoryVersion.CreatedDateTime
que la colonne qui contient la valeur qui représente un « passé » notamment la version d'un BlogStory . Je considère cela beaucoup plus bénéfique qu'un VersionId
ou VersionCode
, car il est plus convivial dans le sens où les gens ont tendance à être plus familiers avec les concepts de temps . Par exemple, les auteurs ou lecteurs de blog peuvent se référer à BlogStoryVersion d'une manière similaire à ce qui suit:
- « Je veux voir la spécifique version du BlogStory identifié par nombre
1750
qui a été créé sur 26 August 2015
à 9:30
».
L' auteur et éditeur Rôles: dérivation et interprétation des données
Avec cette approche, vous pouvez facilement distinguer qui détient le « original » AuthorId
d'un béton BlogStory sélectionnant l'option « plus tôt » la version d'une certaine BlogStoryId
de la BlogStoryVersion
table en vertu de l' application de la fonction MIN () à BlogStoryVersion.CreatedDateTime
.
De cette façon, chaque BlogStoryVersion.AuthorId
valeur contenue dans toutes les lignes des versions "ultérieures" ou "suivantes" indique, naturellement, l' identifiant d' auteur de la version respective à portée de main, mais on peut également dire qu'une telle valeur est, en même temps, dénotant le rôle joué par l'impliqué utilisateur comme éditeur du « original » version d'un BlogStory .
Oui, une AuthorId
valeur donnée peut être partagée par plusieurs BlogStoryVersion
lignes, mais il s'agit en fait d'une information qui indique quelque chose de très significatif sur chaque version , donc la répétition de ladite donnée n'est pas un problème.
Le format des colonnes DATETIME
En ce qui concerne le type de données DATETIME, oui, vous avez raison, " MySQL récupère et affiche les valeurs DATETIME au YYYY-MM-DD HH:MM:SS
format ' ' ", mais vous pouvez entrer en toute confiance les données pertinentes de cette manière, et lorsque vous devez effectuer une requête, vous n'avez qu'à utilisez les fonctions DATE et TIME intégrées afin d'afficher, entre autres, les valeurs concernées au format approprié pour vos utilisateurs. Ou vous pouvez certainement effectuer ce type de formatage de données via le code de votre programme d'application.
Implications des BlogStory
opérations UPDATE
Chaque fois qu'une BlogStory
ligne subit une MISE À JOUR, vous devez vous assurer que les valeurs correspondantes qui étaient «présentes» jusqu'à ce que la modification ait eu lieu sont ensuite INSÉRÉES dans le BlogStoryVersion
tableau. Ainsi, je suggère fortement de réaliser ces opérations au sein d'une seule ACIDE TRANSACTION afin de garantir qu'elles soient traitées comme une Unité de Travail indivisible. Vous pouvez aussi bien utiliser TRIGGERS, mais ils ont tendance à rendre les choses désordonnées, pour ainsi dire.
Présentation d'une VersionId
ou d' une VersionCode
colonne
Si vous choisissez (en raison de circonstances commerciales ou de préférences personnelles) d'incorporer une BlogStory.VersionId
ou une BlogStory.VersionCode
colonne pour distinguer les BlogStoryVersions , vous devez réfléchir aux possibilités suivantes:
A VersionCode
pourrait être requis pour être UNIQUE dans (i) l'ensemble du BlogStory
tableau et également (ii) BlogStoryVersion
.
Par conséquent, vous devez mettre en œuvre une méthode soigneusement testée et totalement fiable afin de générer et d'attribuer chaque Code
valeur.
Peut-être, les VersionCode
valeurs pourraient être répétées dans différentes BlogStory
lignes, mais jamais dupliquées avec la même chose BlogStoryNumber
. Par exemple, vous pourriez avoir:
- un BlogStoryNumber
3
- Version83o7c5c
et, simultanément,
- un BlogStoryNumber
86
- Version83o7c5c
et
- un BlogStoryNumber
958
- Version83o7c5c
.
La dernière possibilité ouvre une autre alternative:
Garder un VersionNumber
pour le BlogStories
, donc il pourrait y avoir:
- BlogStoryNumber
23
- Versions1, 2, 3…
;
- BlogStoryNumber
650
- Versions1, 2, 3…
;
- BlogStoryNumber
2254
- Versions1, 2, 3…
;
- etc.
Tenir les versions «originales» et «ultérieures» dans un seul tableau
Bien que toutes les BlogStoryVersions soient conservées dans la même personne table de base soit possible, je suggère de ne pas le faire car vous mélangeriez deux types de faits (conceptuels) distincts, ce qui a donc des effets secondaires indésirables sur
- contraintes et manipulation des données (au niveau logique), ainsi que
- le traitement et le stockage associés (au niveau physique).
Mais, à condition que vous choisissiez de suivre cette ligne de conduite, vous pouvez toujours profiter de nombreuses idées détaillées ci-dessus, par exemple:
- un PK composite composé d'une colonne INT (
BlogStoryNumber
) et d'une colonne DATETIME ( CreatedDateTime
);
- l'utilisation des fonctions du serveur afin d'optimiser les processus pertinents, et
- les rôles dérivables de l' auteur et de l' éditeur .
Voyant qu'en procédant à une telle approche, une BlogStoryNumber
valeur sera dupliquée dès que des versions «plus récentes» seront ajoutées, une option que vous pourriez évaluer (qui est très similaire à celles mentionnées dans la section précédente) est d'établir un BlogStory
PK composé des colonnes BlogStoryNumber
et VersionCode
, de cette manière, vous seriez en mesure d'identifier de manière unique chaque version d'un BlogStory . Et vous pouvez essayer avec une combinaison de BlogStoryNumber
et VersionNumber
aussi.
Scénario similaire
Vous pouvez trouver ma réponse à cette question d'aide, car je propose également d'activer les capacités temporelles dans la base de données concernée pour faire face à un scénario comparable.