web-dev-qa-db-fra.com

Versioning dans les tables SQL - Comment gérer?

Voici un scénario de fiction avec certaines données peuplées. À des fins fiscales, ma société de fiction doit conserver des enregistrements de données historiques. Pour cette raison, j'ai inclus une colonne de version sur la table.

TABLE EMPLOYEE: (with personal commentary)

|ID | VERSION | NAME       | Position | PAY |
+---+---------+------------+----------+-----+
| 1 |    1    | John Doe   | Owner    | 100 | Started company
| 1 |    2    | John Doe   | Owner    |  80 | Pay cut to hire a coder
| 2 |    1    | Mark May   | Coder    |  20 | Hire said coder
| 2 |    2    | Mark May   | Coder    |  30 | Productive coder gets raise
| 3 |    1    | Jane Field | Admn Asst|  15 | Need office staff
| 2 |    3    | Mark May   | Coder    |  35 | Productive coder gets raise
| 1 |    3    | John Doe   | Owner    | 120 | Sales = profit for owner!
| 3 |    2    | Jane Field | Admn Asst|  20 | Raise for office staff
| 4 |    1    | Cody Munn  | Coder    |  20 | Hire another coder
| 4 |    2    | Cody Munn  | Coder    |  25 | Give that coder raise
| 3 |    3    | Jane Munn  | Admn Asst|  20 | Jane marries Cody <3
| 2 |    4    | Mark May   | Dev Lead |  40 | Promote mark to Dev Lead
| 4 |    3    | Cody Munn  | Coder    |  30 | Give Cody a raise
| 2 |    5    | Mark May   | Retired  |   0 | Mark retires
| 5 |    1    | Joey Trib  | Dev Lead |  40 | Bring outside help for Dev Lead
| 6 |    1    | Hire Meplz | Coder    |  10 | Hire a cheap coder
| 3 |    4    | Jane Munn  | Retired  |   0 | Jane quits
| 7 |    1    | Work Fofre | Admn Asst|  10 | Hire Janes replacement
| 8 |    1    | Fran Hesky | Coder    |  10 | Hire another coder
| 9 |    1    | Deby Olav  | Coder    |  25 | Hire another coder
| 4 |    4    | Cody Munn  | VP Ops   |  80 | Promote Cody
| 9 |    2    | Deby Olav  | VP Ops   |  80 | Cody fails at VP Ops, promote Deby
| 4 |    5    | Cody Munn  | Retired  |   0 | Cody retires in shame
| 5 |    2    | Joey Trib  | Dev Lead |  50 | Give Joey a raise
+---+---------+------------+----------+-----+

Maintenant, si je voulais faire quelque chose comme "Obtenir une liste des codeurs actuels", je ne pouvais pas simplement faire SELECT * FROM EMPLOYEE WHERE Position = 'Coder' Parce que cela reviendrait beaucoup de données historiques ... ce qui est mauvais.

Je cherche de bonnes idées pour gérer ce scénario. Je vois quelques options qui sautent à moi, mais je suis sûr que quelqu'un va dire "Wow, c'est une erreur de recrue, lueur ... Essayez ceci pour la taille:" C'est ce que cet endroit est tout à propos, non? :-)

Numéro d'idée 1 : Gardez une table de version avec la version actuelle comme celle-ci

TABLE EMPLOYEE_VERSION:

|ID |VERSION|
+---+-------+
| 1 |   3   |
| 2 |   5   |
| 3 |   4   |
| 4 |   6   |
| 5 |   2   |
| 6 |   1   |
| 7 |   1   |
| 8 |   1   |
| 9 |   2   |     
+---+-------+

Bien que je ne sois pas sûr de savoir comment je ferais cela avec une seule requête, je suis sûr que cela pourrait être fait et je parie que je pouvais le comprendre avec une quantité d'effort assez faible.

Bien sûr, je devrais mettre à jour cette table chaque fois que j'insérer dans la table des employés pour incrémenter la version pour l'ID donné (ou insérer dans la table de la version lorsqu'un nouvel ID est effectué).

La surcharge de cela semble indésirable.

Idea numéro 2 : Gardez une table d'archive et une table principale. Avant de mettre à jour la table principale, insérez la ligne, je suis sur le point d'écraser dans la table d'archives et utilisez la table principale car je le ferais normalement comme si je n'étais pas préoccupé par la version de la version.

Numéro d'idée 3 : Trouver une requête qui ajoute quelque chose sur les lignes de SELECT * FROM EMPLOYEE WHERE Position = 'Coder' and version=MaxVersionForId(EMPLOYEE.ID)... Pas tout à fait sûr comment je ferais cela. Cela me semble la meilleure idée, mais je ne suis vraiment pas sûr à ce stade.

Numéro d'idée 4 : Faire une colonne de "courant" et ajoutez "où actuellement = vrai et ..."

Cela me survient que cactueusement les gens l'ont fait auparavant, courir dans ces mêmes problèmes et avoir une idée de la part de partager, et donc je viens chercher ça! :) J'ai déjà essayé de trouver des exemples du problème ici déjà, mais ils semblent être spécialisés à un scénario particulier.

Merci!

Edit 1 :

Premièrement, j'apprécie toutes les réponses et vous avez tous dit la même chose - DATE est meilleur que VERSION NUMBER. Une des raisons que j'allais avec VERSION NUMBER C'était de simplifier le processus de mise à jour sur le serveur pour empêcher le scénario suivant.

Personne une Charge Employé record 3 Au cours de sa session, et il dispose de la version 4. Personne B Chars Employé record 3 à sa session, et il dispose de la version 4. La personne A fait des changements et des engagements. Cela fonctionne car la version la plus récente de la base de données est 4. Il est maintenant 5. Personne B apporte des changements et s'engage. Cela échoue parce que la version la plus récente est de 5, tandis que son est 4.

Comment le modèle EFFECTIVE DATE S'adresse-t-il à ce problème?

Edit 2 :

Je pense que je pourrais le faire en faisant quelque chose comme ceci: personne un employé de charges record 3 à sa session et la date d'effet est de 1-1-2010, 13h00, sans autre expérience. Personne B charge les employés record 3 à sa session et la date d'effet est de 1-1-2010, 13h00, sans autre expérience. Personne A fait des changements et s'engage. L'ancienne copie va à la table d'archives (essentiellement une idée 2) avec une date d'experence du 21/22/2010 13h00. La version mise à jour de la table principale a une date d'entrée en vigueur du 21/02/2010 13h00. La personne B apporte des changements et s'engage. Le commit échoue parce que les dates efficaces (dans la base de données et la session) ne correspondent pas.

37
corsiKa

Je pense que vous avez commencé le mauvais chemin.

Généralement, pour la version ou le stockage de données historiques, vous faites l'une des deux (ou les deux) choses.

  1. Vous avez une table séparée qui imite la table d'origine + une colonne de date/heure de la date à laquelle il a été modifié. Chaque fois qu'un enregistrement est mis à jour, vous insérez le contenu existant dans la table d'historique juste avant la mise à jour.

  2. Vous avez une base de données d'entrepôt séparée. Dans ce cas, vous pouvez également la version comme dans le n ° 1 ci-dessus OR Vous êtes simplement instantané une fois de soi aussi souvent (heure, quotidienne, hebdomadaire ..)

Garder votre numéro de version dans la même table que votre normale a plusieurs problèmes. Premièrement, la taille de la table va grandir comme un fou. Cela mettra une pression constante sur des requêtes normales de production.

Deuxièmement, il va augmenter radicalement votre complexité de requête pour les jointures, etc. afin de vous assurer que la dernière version de chaque enregistrement est utilisée.

38
NotMe

Ce que vous avez ici est appelé une dimension changeante lente (SCD). Il existe des méthodes éprouvées pour y faire face:

http://fr.wikipedia.org/wiki/slowly_changeing_dimension

Pensais que j'ajouterais cela puisque personne ne semble l'appeler par nom.

33
Zachary Yates

Voici mon approche suggérée, qui a très bien fonctionné pour moi dans le passé:

  • Oublier le numéro de version. Au lieu de cela, utilisez StartDate et EndDate colonnes
  • Écrivez un déclencheur pour vous assurer qu'il n'y a pas de gammes de date qui se chevauchant pour le même ID, et qu'il n'y a jamais un enregistrement avec un NULL _ EndDate pour le même ID (ceci est votre enregistrement actuellement efficace)
  • Mettre des index sur StartDate et EndDate; Cela devrait vous donner des performances raisonnables

Cela vous laissera facilement faire rapport par date:

select *
from MyTable 
where MyReportDate between StartDate and EndDate

ou obtenir les informations actuelles:

select *
from MyTable 
where EndDate is null
11
RedFilter

Une approche que j'ai conçue pour une base de données récente consiste à utiliser des révisions comme suit:

  • Gardez votre entité dans deux tables:

    1. "Employee" stocke un identifiant de clé primaire et des données que vous ne souhaitez pas être versées (s'il y en a une).

    2. "Employee_Revision" stocke toutes les données saillantes sur l'employé, avec une clé étrangère de la table des employés et une clé étrangère, "RevisionID" à une table appelée "Révision".

  • Faire une nouvelle table appelée "révision". Cela peut être utilisé par toutes les entités de votre base de données, pas seulement un employé. Il contient une colonne d'identité pour la clé primaire (ou AutoNumber, ou quelle que soit votre base de données appelle une telle chose). Il contient également des colonnes efficaces et effectives. J'ai aussi une colonne de texte sur la table - entity_type - pour des raisons de lisibilité humaines qui contiennent le nom de la table de révision principale (dans ce cas "employé"). La table de révision ne contient aucune touche étrangère. La valeur par défaut pour EffectiveDrom est de 1-janvier-1900 et la valeur par défaut pour EffectiveTo est de 31 à 9999. Cela me permet de ne pas simplifier l'interrogation de la date.

Je veille à ce que la table de révision soit bien indexée sur (effectiveFrom, EffectiveVeto, révisionnaire) et également sur (révisionniste, efficaceFrom, EffectiveVeto).

Je peux ensuite utiliser des jointures et des comparaisons simples <> pour sélectionner un enregistrement approprié pour une date. Cela signifie également que les relations entre les entités sont également entièrement versées. En fait, je trouve utile d'utiliser des fonctions de valorisation de la table SQL Server pour permettre une interrogation très simple de n'importe quelle date.

Voici un exemple (en supposant que vous ne voulez pas la version nomination des employés de sorte que s'ils changent de nom, le changement est efficace historiquement).

--------
employee
--------
employee_id  |  employee_name
-----------  |  -------------
12351        |  John Smith

-----------------
employee_revision
-----------------
employee_id  |  revision_id  |  department_id  |  position_id  |  pay
-----------  |  -----------  |  -------------  |  -----------  |  ----------
12351        |  657442       |  72             |  23           |  22000.00
12351        |  657512       |  72             |  27           |  22000.00
12351        |  657983       |  72             |  27           |  28000.00

--------
revision
--------
revision_id  |  effective_from  |  effective_to  |  entity_type
-----------  |  --------------  |  ------------  |  -----------
657442       |  01-Jan-1900     |  03-Mar-2007   |  EMPLOYEE
657512       |  04-Mar-2007     |  22-Jun-2009   |  EMPLOYEE
657983       |  23-Jun-2009     |  31-Dec-9999   |  EMPLOYEE

Un avantage de stocker vos métadonnées de révision dans une table séparée est qu'il est facile de l'appliquer de manière cohérente à toutes vos entités. Un autre est qu'il est plus facile de l'étendre à inclure d'autres choses, telles que des branches ou des scénarios, sans avoir à modifier chaque table. Ma principale raison est qu'il conserve vos principales tables entités claires et épurées.

(Les données et l'exemple ci-dessus sont de fiction - ma base de données ne modélise pas les employés).

11
James Cane

Vous faites certainement cela mal. Garder une base de données fonctionnant doucement nécessite que vous n'avez que la quantité minimale de données dans vos tables de production dont vous avez besoin. Inévitablement tenant des données historiques avec la lecture en direct ajoute une redondance compliquera les requêtes et les performances lentes, ainsi que vos successeurs vont avoir l'air de ressembler vraiment à cela avant de le soumettre au DailyWTF!

Au lieu de cela, créez une copie de la table - EmployeeHistorical par exemple - mais avec la colonne ID non définie comme identity (vous pouvez choisir d'ajouter une nouvelle colonne d'identification supplémentaire et une colonne d'horodatage datériel aussi). Ajoutez ensuite une gâchette à votre table d'employé qui incendie sur Mettre à jour et supprimez et écrit une copie de la ligne complète à la table historique. Et pendant que vous êtes à cela, la capture de l'identifiant de l'utilisateur faisant la modification est souvent utile à des fins d'audit.

En règle générale, lorsque je fais cela sur une table active, j'essaie de créer la table historique dans une base de données différente, notamment que cela réduit la fragmentation (et donc la maintenance) sur votre base de données principale et il est plus facile de gérer les sauvegardes - comme les archives peuvent augmenter. grand.

Vos problèmes concernant la conflit d'édition doivent être traités avec la transaction et les mécanismes de verrouillage de la base de données normaux. CODING ADHOC hacks JOINTS Pour imiter si vous-même est toujours prononcé à la consommation de temps et à l'erreur (une condition de pointe que vous n'avez pas pensé de sauter toujours, et d'écrire des serrures correctement, vous êtes vraiment obligé de grok Sempahores , qui est décidément non trivial)

2
Cruachan

L'idée 3 fonctionnera:

SELECT * FROM EMPLOYEE AS e1
WHERE Position = 'Coder'
AND Version = (
    SELECT MAX(Version) FROM Employee AS e2
    WHERE e1.ID=e2.ID)

Vous voulez vraiment utiliser quelque chose comme une date, ce qui est beaucoup plus facile à programmer et à suivre, et utilisera la même logique (quelque chose comme une colonne effective))

ÉDITER :

Chris est totalement correct sur le déplacement de cette info de votre table de production pour la performance, surtout si vous attendez des mises à jour fréquentes. Une autre option serait de faire une vue Cela ne vous montre que la version la plus récente des informations de chaque personne, que vous établissez de cette table.

2
JNK