web-dev-qa-db-fra.com

Comment contrôler la version d'un enregistrement dans une base de données

Disons que j'ai un enregistrement dans la base de données et que les utilisateurs administrateurs et normaux peuvent effectuer des mises à jour.

Quelqu'un peut-il suggérer une bonne approche/architecture pour contrôler la version de chaque modification dans ce tableau afin de pouvoir restaurer un enregistrement dans une révision précédente.

154
Niels Bosma

Supposons que vous ayez une table FOO que les administrateurs et les utilisateurs peuvent mettre à jour. La plupart du temps, vous pouvez écrire des requêtes sur la table FOO. Jours heureux.

Ensuite, je créerais un FOO_HISTORY table. Cela a toutes les colonnes de la table FOO. La clé primaire est identique à FOO plus une colonne RevisionNumber. Il y a une clé étrangère de FOO_HISTORY à FOO. Vous pouvez également ajouter des colonnes liées à la révision, telles que UserId et RevisionDate. Remplissez les numéros de révision de manière croissante dans tous les *_HISTORY tables (à partir d’une séquence Oracle ou l’équivalent). Ne comptez pas sur un seul changement par seconde (c.-à-d. Ne mettez pas RevisionDate dans la clé primaire).

Maintenant, chaque fois que vous mettez à jour FOO, juste avant de faire la mise à jour, vous insérez les anciennes valeurs dans FOO_HISTORY. Vous faites cela à un niveau fondamental de votre conception afin que les programmeurs ne puissent pas manquer cette étape par inadvertance.

Si vous souhaitez supprimer une ligne de FOO, vous avez le choix. Mettez en cascade et supprimez tout l'historique, ou effectuez une suppression logique en marquant FOO comme supprimé.

Cette solution est utile lorsque vous vous intéressez largement aux valeurs actuelles et de temps en temps à l’historique. Si vous avez toujours besoin de l'historique, vous pouvez indiquer des dates de début et de fin effectives et conserver tous les enregistrements dans FOO. Chaque requête doit ensuite vérifier ces dates.

153
WW.

Je pense que vous recherchez un contrôle de version du contenu des enregistrements de base de données (comme le fait StackOverflow lorsque quelqu'un modifie une question/réponse). Un bon point de départ pourrait être un modèle de base de données utilisant le suivi de révision .

Le meilleur exemple qui me vienne à l’esprit est MediaWiki, le moteur Wikipedia. Comparez le diagramme de la base de données ici , en particulier le table de révision .

Selon les technologies que vous utilisez, vous devrez trouver de bons algorithmes de diff/fusion.

Vérifiez cette question si c'est pour .NET.

40
CMS

Dans le monde de la BI, vous pouvez y parvenir en ajoutant une date de début et une date de fin à la table que vous souhaitez version. Lorsque vous insérez le premier enregistrement dans la table, la date de début est renseignée, mais la date de fin est null. Lorsque vous insérez le deuxième enregistrement, vous mettez également à jour la date de fin du premier enregistrement avec la date de début du deuxième enregistrement.

Lorsque vous souhaitez afficher l'enregistrement en cours, vous sélectionnez celui où endDate est null.

Ceci est parfois appelé type 2 dimension à évolution lente . Voir aussi TupleVersioning

25
Dave Neeley

Mise à niveau vers SQL 2008.

Essayez d’utiliser SQL Change Tracking, en SQL 2008. Au lieu d’horodatage et de hacks des colonnes désactivées, vous pouvez utiliser cette nouvelle fonctionnalité pour suivre les modifications apportées aux données de votre base de données.

Suivi des modifications MSDN SQL 2008

9
D3vtr0n

Je voulais juste ajouter qu’une bonne solution à ce problème consiste à utiliser un base de données temporelle . De nombreux fournisseurs de bases de données offrent cette fonctionnalité soit directement, soit via une extension. J'ai utilisé avec succès l'extension table temporelle avec PostgreSQL, mais d'autres l'ont aussi. Chaque fois que vous mettez à jour un enregistrement dans la base de données, celle-ci conserve également la version précédente de cet enregistrement.

6
wuher

Deux options:

  1. Ayez une table d'historique - insérez les anciennes données dans cette table d'historique chaque fois que l'original est mis à jour.
  2. Table d'audit - stockez les valeurs avant et après - uniquement pour les colonnes modifiées d'une table d'audit, ainsi que d'autres informations, telles que qui a mis à jour et quand.
5
alok

Vous pouvez effectuer un audit sur une table SQL via des déclencheurs SQL. À partir d'un déclencheur, vous pouvez accéder à 2 tables spéciales ( inséré et supprimé ). Ces tables contiennent les lignes exactes insérées ou supprimées à chaque mise à jour de la table. Dans le déclencheur SQL, vous pouvez prendre ces lignes modifiées et les insérer dans la table d'audit. Cette approche signifie que votre audit est transparent pour le programmeur. n'exigeant aucun effort de leur part ni aucune connaissance de la mise en œuvre.

L'avantage supplémentaire de cette approche est que l'audit aura lieu, que l'opération SQL ait eu lieu via vos DLL d'accès aux données ou via une requête SQL manuelle. (car l'audit est effectué sur le serveur lui-même).

4
Doctor Jones

Vous ne dites pas quelle base de données, et je ne le vois pas dans les balises post. Si c'est pour Oracle, je peux recommander l'approche intégrée dans Designer: utilisez tables journal . Si c'est pour une autre base de données, eh bien, je recommande fondamentalement la même manière, aussi ...

La façon dont cela fonctionne, si vous souhaitez la répliquer dans une autre base de données, ou si vous souhaitez simplement la comprendre, est que, pour une table, une table fantôme est également créée, mais uniquement une table de base de données normale, avec les mêmes spécifications de champ. , plus quelques champs supplémentaires: comme quelle action a été exécutée en dernier (chaîne, valeurs typiques "INS" pour insertion, "UPD" pour mise à jour et "DEL" pour suppression), date/heure pour le moment où l'action a eu lieu, et ID utilisateur pour qui a fait il.

Par le biais de déclencheurs, chaque action sur une ligne de la table insère une nouvelle ligne dans la table de journal avec les nouvelles valeurs, l'action exécutée, l'heure et par quel utilisateur. Vous ne supprimez jamais aucune ligne (du moins pas au cours des derniers mois). Oui, ça va devenir gros, facilement des millions de lignes, mais vous pouvez facilement suivre la valeur de tout enregistrement à n'importe quel moment depuis le début de la journalisation ou la dernière purge des anciennes lignes du journal et qui a effectué le dernier changement.

Dans Oracle, tout ce dont vous avez besoin est généré automatiquement sous forme de code SQL. Tout ce que vous avez à faire est de le compiler/exécuter; et il vient avec une application de base CRUD (en réalité seulement "R") pour l'inspecter.

3
bart

Je fais aussi la même chose. Je crée une base de données pour les plans de cours. Ces plans nécessitent une flexibilité de gestion des versions de changement atomique. En d’autres termes, chaque modification, aussi minime soit-elle, des plans de cours doit être autorisée, mais l’ancienne version doit également rester intacte. De cette façon, les créateurs de cours peuvent modifier les plans de cours pendant que les élèves les utilisent.

La façon dont cela fonctionnerait est qu'une fois qu'un élève a terminé une leçon, ses résultats sont attachés à la version qu'ils ont terminée. Si une modification est apportée, leurs résultats indiqueront toujours leur version.

De cette façon, si un critère de leçon est supprimé ou déplacé, ses résultats ne changeront pas.

Pour ce faire, je gère toutes les données d’un tableau. Normalement, je n’aurais qu’un champ d’identifiant, mais avec ce système, j’utilise un identifiant et un sub_id. Le sous_id reste toujours avec la ligne, par le biais de mises à jour et de suppressions. L'identifiant est auto-incrémenté. Le logiciel de planification de cours établira un lien avec le plus récent sous_id. Les résultats des étudiants seront liés à l'identifiant. J'ai également inclus un horodatage pour suivre le moment des modifications, mais il n'est pas nécessaire de gérer le contrôle de version.

Une chose que je pourrais changer, une fois que je l’aurais testée, c’est que je pourrais utiliser l’idée de finDate null mentionnée précédemment. Dans mon système, pour trouver la version la plus récente, il me faudrait trouver le max (id). L'autre système recherche simplement endDate = null. Vous ne savez pas si les avantages sont évidents avec un autre champ de date.

Mes deux centimes.

2
Jordan

Alors que @WW. La réponse est une bonne réponse. Une autre solution consiste à créer une colonne de version et à conserver toutes vos versions dans le même tableau.

Pour une approche de table vous pouvez soit:

  • Utilisez un drapeau pour indiquer la dernière lettre Word Appuyez sur
  • OU faites une version supérieure à la version outer join.

Un exemple de code SQL de la méthode outer join À l'aide de numéros de révision est le suivant:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

La mauvaise nouvelle est que ce qui précède nécessite un outer join Et que les jointures externes peuvent être lentes. La bonne nouvelle est que la création de nouvelles entrées est théoriquement moins chère, car vous pouvez le faire en en une opération d'écriture sans transaction (en supposant que votre base de données est atomique).

Voici un exemple de nouvelle révision pour '/stuff':

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

Nous insérons en utilisant les anciennes données. Ceci est particulièrement utile si vous voulez uniquement mettre à jour une colonne et éviter le verrouillage optimiste et/ou les transactions.

L'approche par drapeau et par table d'historique nécessite deux lignes à insérer/mettre à jour.

L’autre avantage de l’approche avec numéro de révision outer join Est que vous pouvez toujours refactoriser ultérieurement l’approche avec plusieurs tables avec des déclencheurs, car votre déclencheur doit essentiellement procéder de la même manière.

2
Adam Gent

Alok suggéra Audit table ci-dessus, je voudrais l'expliquer dans mon post.

J'ai adopté cette conception de table unique sans schéma sur mon projet.

schéma:

  • id - INTEGER AUTO INCREMENT
  • nom d'utilisateur - STRING
  • nom de la table - STRING
  • oldvalue - TEXT/JSON
  • newvalue - TEXT/JSON
  • créé le - DATETIME

Cette table peut contenir des enregistrements historiques pour chaque table en un seul endroit, avec l'historique complet des objets dans un enregistrement. Cette table peut être remplie à l'aide de déclencheurs/crochets dans lesquels les données sont modifiées, stockant ainsi les instantanés anciens et nouveaux de la ligne cible.

avec cette conception:

  • Moins de tables à gérer pour la gestion de l'historique.
  • Stocke un instantané complet de chaque état, ancien et nouveau, de la ligne.
  • Facile à chercher sur chaque table.
  • Peut créer une partition par table.
  • Peut définir une politique de conservation des données par table.

Inconvénients avec cette conception:

  • La taille des données peut être volumineuse si le système subit des modifications fréquentes.
2
Hassan Farid