Comment stocker les données historiques

162

Certains collègues et moi nous sommes lancés dans un débat sur la meilleure façon de stocker les données historiques. Actuellement, pour certains systèmes, j'utilise une table séparée pour stocker les données historiques et je garde une table originale pour l'enregistrement actif actuel. Alors, disons que j'ai la table FOO. Sous mon système, tous les enregistrements actifs iront dans FOO, et tous les enregistrements historiques iront dans FOO_Hist. De nombreux champs différents dans FOO peuvent être mis à jour par l'utilisateur, je veux donc garder un compte précis de tout mis à jour. FOO_Hist contient exactement les mêmes champs que FOO à l'exception d'un HIST_ID auto-incrémenté. Chaque FOO temps est mis à jour, j'effectue une déclaration d'insertion dans FOO_Hist similaire à: insert into FOO_HIST select * from FOO where id = @id.

Mon collègue dit que c'est une mauvaise conception parce que je ne devrais pas avoir une copie exacte d'une table pour des raisons historiques et que je devrais simplement insérer un autre enregistrement dans la table active avec un indicateur indiquant que c'est à des fins historiques.

Existe-t-il une norme pour gérer le stockage des données historiques? Il me semble que je ne veux pas encombrer mes enregistrements actifs avec tous mes enregistrements historiques dans le même tableau, étant donné que cela peut être bien plus d'un million d'enregistrements (je pense à long terme).

Comment vous ou votre entreprise gérez-vous cela?

J'utilise MS SQL Server 2008, mais j'aimerais garder la réponse générique et arbitraire de tout SGBD.

Aaron
la source

Réponses:

80

La prise en charge des données historiques directement dans un système opérationnel rendra votre application beaucoup plus complexe qu'elle ne le serait autrement. En général, je ne recommanderais pas de le faire à moins que vous ne soyez obligé de manipuler les versions historiques d'un enregistrement dans le système.

Si vous regardez de près, la plupart des exigences relatives aux données historiques appartiennent à l'une des deux catégories suivantes:

  • Journalisation d'audit: il est préférable d'utiliser des tables d'audit. Il est assez facile d'écrire un outil qui génère des scripts pour créer des tables de journaux d'audit et des déclencheurs en lisant les métadonnées du dictionnaire de données système. Ce type d'outil peut être utilisé pour moderniser la journalisation d'audit sur la plupart des systèmes. Vous pouvez également utiliser ce sous-système pour la capture de données modifiées si vous souhaitez implémenter un entrepôt de données (voir ci-dessous).

  • Rapports historiques: rapports sur l'état historique, les positions «as-at» ou rapports analytiques au fil du temps. Il peut être possible de répondre à de simples exigences de rapport historique en interrogeant des tables de journalisation d'audit du type décrit ci-dessus. Si vous avez des exigences plus complexes, il peut être plus économique de mettre en place un magasin de données pour le reporting que d'essayer d'intégrer l'historique directement dans le système opérationnel.

    Les dimensions à évolution lente sont de loin le mécanisme le plus simple pour suivre et interroger l'état de l'historique et une grande partie du suivi de l'historique peut être automatisée. Les gestionnaires génériques ne sont pas si difficiles à écrire. En règle générale, les rapports historiques n'ont pas besoin d'utiliser des données à la minute, donc un mécanisme d'actualisation par lots convient normalement. Cela permet de conserver une architecture de base et de système de reporting relativement simple.

Si vos besoins entrent dans l'une de ces deux catégories, il vaut probablement mieux ne pas stocker les données historiques dans votre système opérationnel. Séparer la fonctionnalité historique en un autre sous-système demandera probablement moins d'efforts dans l'ensemble et produira des bases de données transactionnelles et d'audit / de reporting qui fonctionnent beaucoup mieux pour leur objectif prévu.

ConcernedOfTunbridgeWells
la source
Je pense que je vois ce que vous dites. Donc, ce que j'ai fait avec ma table FOO_Hist était vraiment de créer une table d'audit. Au lieu d'utiliser un déclencheur à insérer dans la table d'audit lors de la mise à jour, je viens d'exécuter une déclaration dans le programme. Est-ce exact?
Aaron
6
Plutôt. Il est cependant préférable de faire ce type de journalisation d'audit avec des déclencheurs; les déclencheurs garantissent que toutes les modifications (y compris les corrections manuelles de données) sont enregistrées dans les journaux d'audit. Si vous avez plus de 10 à 20 tables à auditer, il est probablement plus rapide dans l'ensemble de créer un outil générateur de déclencheurs. Si le trafic disque pour les journaux d'audit pose un problème, vous pouvez placer les tables du journal d'audit sur un ensemble distinct de disques.
ConcernedOfTunbridgeWells
Oui, je suis à 100% d'accord avec cela. Je vous remercie.
Aaron
40

Je ne pense pas qu'il y ait une manière standard particulière de le faire, mais j'ai pensé que j'y ajouterais une méthode possible. Je travaille dans Oracle et notre cadre d'application Web interne qui utilise XML pour stocker les données d'application.

Nous utilisons quelque chose appelé un modèle Master - Detail qui, dans sa plus simple expression, consiste en:

Master Table par exemple appelée Widgetssouvent contenant juste un ID. Contiendra souvent des données qui ne changeront pas avec le temps / qui ne sont pas historiques.

Table de détail / historique par exemple appelée Widget_Detailscontenant au moins:

  • ID - clé primaire. Détail / ID historique
  • MASTER_ID - par exemple dans ce cas appelé 'WIDGET_ID', il s'agit du FK de l'enregistrement maître
  • START_DATETIME - horodatage indiquant le début de cette ligne de base de données
  • END_DATETIME - horodatage indiquant la fin de cette ligne de base de données
  • STATUS_CONTROL - une colonne de caractères unique indique l'état de la ligne. «C» indique le courant, NULL ou «A» serait historique / archivé. Nous l'utilisons uniquement car nous ne pouvons pas indexer le END_DATETIME étant NULL
  • CREATED_BY_WUA_ID - stocke l'ID du compte qui a provoqué la création de la ligne
  • XMLDATA - stocke les données réelles

Donc, essentiellement, une entité commence par avoir 1 ligne dans le maître et 1 ligne dans le détail. Le détail ayant une date de fin NULL et STATUS_CONTROL de 'C'. Lorsqu'une mise à jour se produit, la ligne actuelle est mise à jour pour avoir END_DATETIME de l'heure actuelle et status_control est défini sur NULL (ou «A» si vous préférez). Une nouvelle ligne est créée dans la table détaillée, toujours liée au même maître, avec status_control 'C', l'identifiant de la personne effectuant la mise à jour et les nouvelles données stockées dans la colonne XMLDATA.

C'est la base de notre modèle historique. La logique de création / mise à jour est gérée dans un package Oracle PL / SQL de sorte que vous transmettez simplement à la fonction l'ID actuel, votre ID utilisateur et les nouvelles données XML et en interne, il effectue toute la mise à jour / l'insertion de lignes pour représenter cela dans le modèle historique . Les heures de début et de fin indiquent quand cette ligne du tableau est active pendant.

Le stockage est bon marché, nous ne SUPPRIMONS généralement pas les données et préférons conserver une piste d'audit. Cela nous permet de voir à quoi ressemblaient nos données à un moment donné. En indexant status_control = 'C' ou en utilisant une vue, l'encombrement n'est pas exactement un problème. Évidemment, vos requêtes doivent prendre en compte que vous devez toujours utiliser la version actuelle (NULL end_datetime et status_control = 'C') d'un enregistrement.

Chris Cameron-Mills
la source
Salut Chris, si vous faites cela, l'ID (clé primaire) doit être changé, non? que diriez-vous du relationnel avec une autre table si elle est utilisée par une autre?
projo
@projo l'ID sur votre table maître est le PK et conceptuellement le «PK» pour le concept que vous traitez. L'ID sur la table de détail est le PK pour identifier une version historique pour le maître (qui est une autre colonne sur le détail). Lors de la formation de relations, vous référeriez souvent le vrai PK de votre concept (c'est-à-dire l'ID sur votre table maître ou la colonne MASTER_ID sur votre détail) et utilisez STATUS_CONTROL = 'C' pour vous assurer que vous obtenez la version actuelle. Vous pouvez également référencer l'ID de détail pour relier quelque chose à un moment particulier.
Chris Cameron-Mills
+1 J'ai implémenté ce modèle avec beaucoup de succès sur plusieurs grands projets.
Three Value Logic
Nous utilisons la même approche.Mais maintenant, je me demande s'il vaut mieux ne stocker que START_DATETIME et ne pas stocker END_DATETIME
bat_ventzi
Quelques variations dans mon expérience. Si votre entité est "terminée", c'est-à-dire archivée ou supprimée, vous pourriez en fait ne pas avoir d'enregistrements détaillés avec contrôle de statut "C", c'est-à-dire pas de ligne courante, bien que vous ne sachiez pas quand cela s'est produit. Vous pouvez également définir un end_datetime sur la dernière ligne et la présence d'une ligne «C» peut indiquer que l'entité est maintenant supprimée / archivée. Enfin, vous pouvez le représenter dans une autre colonne, STATUS, que vous avez probablement déjà.
Chris Cameron-Mills
15

Je pense que votre approche est correcte. La table historique doit être une copie de la table principale sans index, assurez-vous que vous avez également l'horodatage de mise à jour dans la table.

Si vous essayez l'autre approche assez tôt, vous rencontrerez des problèmes:

  • frais généraux de maintenance
  • plus de drapeaux dans les sélections
  • ralentissement des requêtes
  • croissance des tables, des index
Alexandre
la source
7

Dans SQL Server 2016 et versions ultérieures , il existe une nouvelle fonctionnalité appelée Tables temporelles qui vise à résoudre ce problème avec un effort minimal de la part du développeur . Le concept de table temporelle est similaire à Change Data Capture (CDC), à la différence que la table temporelle a résumé la plupart des choses que vous deviez faire manuellement si vous utilisiez CDC.

Benjamin
la source
1

Je voulais juste ajouter une option que j'ai commencé à utiliser parce que j'utilise Azure SQL et que la chose à plusieurs tables était beaucoup trop lourde pour moi. J'ai ajouté un déclencheur d'insertion / mise à jour / suppression sur ma table, puis j'ai converti le changement avant / après en json en utilisant la fonction "FOR JSON AUTO".

 SET @beforeJson = (SELECT * FROM DELETED FOR JSON AUTO)
SET @afterJson = (SELECT * FROM INSERTED FOR JSON AUTO)

Cela renvoie une représentation JSON de l'enregistrement avant / après la modification. Je stocke ensuite ces valeurs dans une table d'historique avec un horodatage du moment où le changement s'est produit (je stocke également l'ID de l'enregistrement actuel concerné). À l'aide du processus de sérialisation, je peux contrôler la manière dont les données sont remplies en cas de modification du schéma.

J'ai appris cela à partir de ce lien ici

JakeHova
la source
0

Vous pouvez simplement partitionner les tables non?

«Stratégies de table partitionnée et d'index à l'aide de SQL Server 2008 Lorsqu'une table de base de données atteint des centaines de gigaoctets ou plus, il peut devenir plus difficile de charger de nouvelles données, de supprimer les anciennes données et de gérer les index. Juste la taille de la table rend ces opérations beaucoup plus longues. Même les données qui doivent être chargées ou supprimées peuvent être très importantes, ce qui rend les opérations INSERT et DELETE sur la table peu pratiques. Le logiciel de base de données Microsoft SQL Server 2008 fournit le partitionnement des tables pour rendre ces opérations plus faciles à gérer. "

clyc
la source
Oui, je peux partitionner les tables, mais est-ce la norme en ce qui concerne les données historiques? Les données historiques devraient-elles être incluses dans le même tableau que les données actives? Telles sont les questions dont je voulais discuter. Ce n'est pas non plus arbitraire car il concerne SQL Server 2008.
Aaron
0

La vraie question est de savoir si vous devez utiliser les données historiques et les données actives ensemble pour le reporting? Si tel est le cas, conservez-les dans une table, partitionnez et créez une vue pour les enregistrements actifs à utiliser dans les requêtes actives. Si vous n'avez besoin de les regarder qu'occasionnellement (pour rechercher des problèmes légaux ou autres), mettez-les dans un tableau séparé.

HLGEM
la source
2
Est-il plus difficile de JOINcréer deux tableaux dans quelques rapports historiques ou est-il plus difficile de modifier chaque insertion / mise à jour / suppression de tableau pour être conscient des problèmes historiques? En fait, un journal d'audit inclurait même les données actuelles dans la table d'historique, de sorte que la table actuelle ne devrait même pas être nécessaire dans un rapport.
0

Une autre option consiste à archiver les données opérationnelles sur une base [quotidienne | horaire | peu importe]. La plupart des moteurs de base de données prennent en charge l'extraction des données dans une archive .

Fondamentalement, l'idée est de créer une tâche Windows ou CRON planifiée qui

  1. détermine les tables actuelles dans la base de données opérationnelle
  2. sélectionne toutes les données de chaque table dans un fichier CSV ou XML
  3. compresse les données exportées dans un fichier ZIP, de préférence avec l'horodatage de la génération dans le nom du fichier pour faciliter l'archivage.

De nombreux moteurs de base de données SQL sont fournis avec un outil qui peut être utilisé à cette fin. Par exemple, lors de l'utilisation de MySQL sous Linux, la commande suivante peut être utilisée dans un travail CRON pour planifier l'extraction:

mysqldump --all-databases --xml --lock-tables=false -ppassword | gzip -c | cat > /media/bak/servername-$(date +%Y-%m-%d)-mysql.xml.gz
Michael
la source
2
Ce n'est pas du tout approprié pour les données historiques car si quelqu'un modifie une valeur et la remet à jour dans le cycle d'archivage, ces mises à jour sont perdues. Il n'existe pas non plus de moyen simple d'examiner les modifications apportées à une entité au fil du temps ou de restaurer partiellement une entité.
Sgoettschkes
0

Je connais cet ancien article mais je voulais juste ajouter quelques points. La norme pour de tels problèmes est ce qui fonctionne le mieux pour la situation. comprendre la nécessité d'un tel stockage et l'utilisation potentielle des données historiques / d'audit / de suivi des modifications est très important.

Audit (objectif de sécurité) : utilisez une table commune pour toutes vos tables auditables. définir la structure pour stocker le nom de la colonne, avant la valeur et après les champs de valeur.

Archive / Historique : pour les cas tels que le suivi de l'adresse précédente, du numéro de téléphone, etc., la création d'une table séparée FOO_HIST est préférable si votre schéma de table de transactions actif ne change pas de manière significative à l'avenir (si votre table d'historique doit avoir la même structure). si vous prévoyez une normalisation de table, un changement de type de données, l'ajout / la suppression de colonnes, stockez vos données historiques au format xml. définir une table avec les colonnes suivantes (ID, Date, Version du schéma, XMLData). cela gérera facilement les changements de schéma. mais vous devez gérer xml et cela pourrait introduire un niveau de complication pour la récupération de données.

Danny D
la source