2016-08-01 1 views
1

Je parcourais ce post pour la versionnalisation au niveau des tables. J'ai remarqué que l'architecture traite de l'utilisation des tables d'histoire. Cependant, mon scénario ne nécessite pas d'annulation mais de récupération des enregistrements ponctuels dans le temps. C'est là que j'ai essayé avec un design sur l'utilisation d'une seule table pour le versioning. Notez qu'il s'agit de données de table sans os (pas de contraintes, d'index, etc.). J'ai l'intention d'indexer basé sur l'identification puisque ceci implique la clause de groupe par sur la colonne.Versionnement de base de données sans tables d'historique

Par exemple, j'ai obtenu un test de table où

id

est l'identifiant,

modstamp est l'horodatage des données (jamais nulles)

En plus de la colonnes ci-dessus, la table contiendra des colonnes de comptabilité

local_modstamp est le t imestamp au cours de laquelle le dossier a été mis à jour

del_modstamp est l'horodatage auquel l'enregistrement a été supprimé

Pendant la sauvegarde, tous les enregistrements sont obtenus à partir de la source et inséré où les enregistrements auraient les valeurs local_modstamp = null et del_stamp = null.

id |modstamp     |local_modstamp |del_modstamp | 
---|---------------------------|---------------|-------------| 
1 |2016-08-01 15:35:32 +00:00 |    |    | 
2 |2016-07-29 13:39:45 +00:00 |    |    | 
3 |2016-07-21 10:15:09 +00:00 |    |    | 

Une fois que les dossiers sont obtenus, ceux-ci sont les scénarios de traitement des données (en supposant que le temps de référence [ref_time] est le moment où le processus est exécuté):

  1. Insérer comme d'habitude.

  2. Mise à jour: mise à jour de l'enregistrement le plus récent avec local_modstamp = ref_time. Puis insérez le nouvel enregistrement. La requête serait: mise à jour = Test set local_modstamp où id = et local_modstamp est non nul et del_modstamp est non nulle insert en valeurs de test (...)

  3. Supprimer: Mettre à jour le disque le plus récent avec del_modstamp = ref_time. Test de mise à jour définie del_modstamp = où id = et local_modstamp est non nul et del_modstamp est non nul

L'objectif de conception à obtenir les derniers enregistrements où local_modstamp n'est pas nulle et del_modstamp est non nul. Cependant, je suis tombé sur un problème où je compte récupérer point dans le temps en utilisant la requête (plus interne requête):

select id, max(modstamp) from test where modstamp <= <ref_time> and (del_modstamp is null || del_modstamp <= <ref_time>) group by id; 

Il semble que je l'ai fait une erreur (ai-je?) D'utiliser NULL un espace réservé pour identifier les derniers enregistrements de la table. Existe-t-il un moyen d'utiliser la conception existante pour obtenir les enregistrements ponctuels?

Sinon, je suppose que la solution probable est de définir le local_modstamp sur les derniers enregistrements. Cela nécessiterait de mettre à jour la logique en utilisant max (local_modstamp) en cas de mises à jour. Est-ce que je peux persister sur mon architecture existante pour obtenir la récupération des données ponctuelles?

J'utilise SQL-Server maintenant, mais cette conception peut également être étendue à d'autres produits de base de données. J'ai l'intention d'utiliser une approche plus générale pour récupérer les données au lieu d'utiliser le fournisseur spécifique hacks.

Répondre

1

Présentation de la forme normale. Considérez ce tableau:

create table Entities(
    ID  int identity primary key, 
    S1  [type], -- Static data 
    Sn  [type], -- more static data 
    V1  [type], -- Volatile data 
    Vn  [type] -- more volatile data 
); 

Les données statiques sont des données qui ne change pas pendant la durée de vie de l'entité ou qui ne nécessite pas le suivi. Les changements de données volatiles et ces changements doivent être suivis.

Déplacer les attributs volatiles à une table séparée:

create table EntityVersions(
    ID  int not null, 
    Effective date not null default sysdate(), 
    Deleted bit not null default 0, 
    V1  [type], 
    Vn  [type], 
    constraint PK_EntityVersions primary key(ID, Effective), 
    constraint FK_EntityVersionEntity foreign key(ID) 
     references Entities(ID) 
); 

Le tableau des entités ne contient plus les attributs volatils.

Une opération d'insertion crée l'enregistrement d'entité maître avec des données statiques, en générant la valeur d'ID unique. Cette valeur est utilisée pour insérer la première version avec les valeurs initiales des données volatiles. Une mise à jour ne fait généralement rien à la table principale (sauf si une valeur statique est réellement modifiée) et une nouvelle version des nouvelles données volatiles est écrite dans la table de versions. Notez qu'aucune modification n'est apportée aux versions existantes, en particulier la dernière version ou "actuelle". La nouvelle version est insérée, fin de l'opération. Pour "annuler" la dernière version ou toute version, supprimez simplement cette version de la table des versions.

Par exemple, une table Employés avec les attributs suivants:

EmployeeNum, HireDate, FirstName, LastName, PayRate, Dept, PhoneExt 

EmployeeNum sera, bien sûr, être statique avec HireDate et FirstName. PhoneExt peut changer de temps en temps mais on s'en fout. Donc, il est désigné statique. La conception finale est:

Employees_S 
=========== 
    EmployeeNum (PK), HireDate, FirstName, PhoneExt 

Employees_V 
=========== 
    EmployeeNum (PK), Effective (PK), IsDeleted, LastName, PayRate, Dept 

Le 1er janvier 2016, nous avons embauché Sally Smith. Les données statiques sont insérées dans Employees_S générant une valeur EmployeeNum de 1001. Nous utilisons cette valeur pour insérer également la première version.

Employees_S 
=========== 
    1001, 2016-01-01, Sally, 12345 

Employees_V 
=========== 
    1001, 2016-01-01, 0, Smith, 35.00, Eng 

le 1 mars, elle obtient une augmentation de salaire:

Employees_S 
=========== 
    1001, 2016-01-01, Sally, 12345 

Employees_V 
=========== 
    1001, 2016-01-01, 0, Smith, 35.00, Eng 
    1001, 2016-03-01, 0, Smith, 40.00, Eng 

Le 1er mai, elle se marie:

Employees_S 
=========== 
    1001, 2016-01-01, Sally, 12345 

Employees_V 
=========== 
    1001, 2016-01-01, 0, Smith, 35.00, Eng 
    1001, 2016-03-01, 0, Smith, 40.00, Eng 
    1001, 2016-05-01, 0, Jones, 40.00, Eng 

Notez que les versions de la même entité, autre que le restriction que les dates d'entrée en vigueur ne peuvent pas être les mêmes, sont complètement indépendants les uns des autres.

Pour voir ce que l'état actuel de l'employé 1001 ressemble, voici la requête:

select s.EmployeeNum, s.HireDate, s.FirstName, v.LastName, v.PayRate, v.Dept, s.PhoneExt 
from Employees_S s 
join Employees_V v 
    on v.EmployeeNum = s.EmployeeNum 
    and v.Effective = (select Max(Effective) 
         from Employees_V 
         where EmployeeNum = v.EmployeeNum 
          and Effective <= SysDate()) 
where s.EmployeeNum = 1001 
    and v.IsDeleted = 0; 

Voici la partie cool. Pour voir ce que l'état de l'employé 1001 ressemblait à, disons le 11 février, voici la requête:

select s.EmployeeNum, s.HireDate, s.FirstName, v.LastName, v.PayRate, v.Dept, s.PhoneExt 
from Employees_S s 
join Employees_V v 
    on v.EmployeeNum = s.EmployeeNum 
    and v.Effective = (select Max(Effective) 
         from Employees_V 
         where EmployeeNum = v.EmployeeNum 
          and Effective <= '2016-02-11') 
where s.EmployeeNum = 1001 
    and v.IsDeleted = 0; 

Il est la même requête - à l'exception de la dernière ligne de la sous-requête. Les données actuelles et historiques résident dans la même table et sont interrogées avec la même instruction.

Voici une autre fonctionnalité intéressante. Nous sommes le 1er juillet et nous savons que le 1er septembre, Sally va être transférée au département marketing, avec une autre augmentation de salaire. La paperasse est déjà passée. Allez-y et insérez les nouvelles données:

Employees_S 
=========== 
    1001, 2016-01-01, Sally, 12345 

Employees_V 
=========== 
    1001, 2016-01-01, 0, Smith, 35.00, Eng 
    1001, 2016-03-01, 0, Smith, 40.00, Eng 
    1001, 2016-05-01, 0, Jones, 40.00, Eng 
    1001, 2016-09-01, 0, Jones, 50.00, Mkt 

La prochaine à dernière version affichera encore comme la version actuelle, mais la première requête exécutée à partir du 1 septembre affichera les données de marketing.

Here sont les diapositives d'une présentation que j'ai faite à quelques reprises lors de foires technologiques. Il contient plus de détails sur la façon dont tout ce qui précède peut être fait, y compris les requêtes. Et here est un document qui va dans beaucoup plus de détails.

+0

Si j'effectue une suppression, la dernière version sera-t-elle marquée avec 1 ou est-elle insérée, puis marquez la suppression avec 1? Cela affectera la requête 'and v.IsDeleted = 0;' dans la requête. Par exemple, Sally quitte l'entreprise le 2016-10-01 mais je dois demander quel est l'état le 2016-06-01, ignorera isDeleted dans la clause atteindre le but? Je suis dans une énigme quant à la normalisation des tables. Je fais un backing autour de quelques milliers dans un schéma et si la normalisation en deux ne va pas gonfler la table. Cela peut-il être porté sur la même table? – dmachop

+0

S'il n'y a pas de données statiques et que chaque colonne est versionnée dans la table des versions, la solution ci-dessus peut être effectuée avec une seule table. Chaque fois que vous sélectionnez un point dans le temps ou les dernières données, il faudrait créer une table temporaire (sélectionnez distinct (id) à partir de la version), puis effectuez une jointure si nécessaire. – dmachop

+0

Le PK est statique et l'expérience montre qu'il n'y a rarement d'autres attributs statiques. Cependant, vous avez toujours besoin de deux tables même si la table principale contient uniquement le PK. Vous avez besoin d'une table maître non versionnée pour être la cible des FK provenant d'autres tables. Si vous lisez les différents schémas de gestion des versions, le grand problème insurmontable a toujours été le manque d'intégrité référentielle. Ceci (vnf) résout ce problème. – TommCatt