Meilleures pratiques pour les tableaux historiques / temporels?

11

Supposons que j'ai un objet, avec certains champs dont je veux suivre l'historique et certains champs que je ne veux pas suivre l'historique. Du point de vue de la normalisation, le schéma suivant est-il correct:

CREATE TABLE MyObject AS (
    MyObjectId INT IDENTITY NOT NULL PRIMARY KEY,
    MyObjectField1 VARCHAR(100) NOT NULL,
    MyObjectField2 VARCHAR(100) NOT NULL,
    MyObjectField3 VARCHAR(100) NOT NULL,
    MyObjectTrackedField1 VARCHAR(100) NOT NULL,
    MyObjectTrackedField2 VARCHAR(100) NOT NULL,
    MyObjectTrackedField3 VARCHAR(100) NOT NULL,
)
CREATE TABLE MyObjectHistory AS (
    MyObjectHistoryId INT IDENTITY NOT NULL PRIMARY KEY,
    MyObjectId INT NOT NULL FOREIGN KEY REFERENCES MyObject(MyObjectId),
    MyObjectTrackedField1 VARCHAR(100) NOT NULL,
    MyObjectTrackedField2 VARCHAR(100) NOT NULL,
    MyObjectTrackedField3 VARCHAR(100) NOT NULL,
)

où MyObjectHistory contient les champs suivis pour tous sauf pour la dernière révision. Ou, si tous les champs suivis doivent être dans une table, et toutes les révisions, y compris la dernière, doivent être dans cette table, comme dans:

CREATE TABLE MyObject AS (
    MyObjectId INT IDENTITY NOT NULL PRIMARY KEY,
    MyObjectField1 VARCHAR(100) NOT NULL,
    MyObjectField2 VARCHAR(100) NOT NULL,
    MyObjectField3 VARCHAR(100) NOT NULL,
)
CREATE TABLE MyObjectHistory AS (
    MyObjectHistoryId INT IDENTITY NOT NULL PRIMARY KEY,
    MyObjectId INT NOT NULL FOREIGN KEY REFERENCES MyObject(MyObjectId),
    MyObjectTrackedField1 VARCHAR(100) NOT NULL,
    MyObjectTrackedField2 VARCHAR(100) NOT NULL,
    MyObjectTrackedField3 VARCHAR(100) NOT NULL,
)
cubetwo1729
la source
Je suis d'accord avec @Joel
HaBo

Réponses:

7

Pour des raisons pratiques d'accès aux données, vous devez utiliser la structure de votre première option, mais conserver à la place toutes les versions de vos valeurs de colonne suivies, y compris la version actuelle dans votre tableau d'historique.

La raison en est qu'en général, lorsque vous souhaitez consulter l'historique, vous souhaitez inclure la version actuelle et toutes les versions antérieures. Lorsque vous ne voulez pas regarder l'histoire, vous la voulez à l'écart. Dans de nombreux cas, cela signifie aller jusqu'à séparer l'historique dans un schéma ou une base de données séparée. Même si vous conservez votre historique dans le même schéma que vos données actuelles, toutes les requêtes qui regardent les données historiques (y compris les valeurs actuelles) seront beaucoup plus complexes car elles doivent essentiellement réunir deux sources.

Joel Brown
la source
2

Je préférerais la première version car vous n'avez probablement que rarement besoin de voir l'historique mais vous aurez souvent besoin de voir la valeur actuelle. Une table d'historique doit être remplie à partir d'un déclencheur, vous n'avez donc pas à vous soucier de la désynchronisation des données en général. Supposons donc que vous ayez un million d'enregistrements dans MyObject, puis 10 000 000 d'enregistrements dans MyObjectHistory. Voulez-vous vraiment vous joindre à une table contenant autant d'enregistrements pour obtenir la valeur actuelle?

Maintenant, si vous devez interroger l'historique aussi fréquemment ou plus fréquemment que la valeur actuelle, alors la deuxième structure fonctionnera. (Et si vous allez afficher la valeur à une date particulière, j'aurais un champ de début et de fin pour simplifier les requêtes.)

BTW J'ajouterais un champ de date à la table d'historique pour pouvoir dire dans quel ordre les changements se sont produits. Vous ne pouvez pas compter sur les identités pour l'ordre temporel. PLus s'il y a une question sur une valeur précédente et quand elle a changé, vous devrez en savoir deux. Je pourrais également mettre des valeurs pour l'application d'où provient le changement (si vous avez plusieurs applications) et / ou la personne qui a effectué le changement.

HLGEM
la source
0

Il y a quelques raisons importantes pour # 1. Le premier est le problème de taille signalé par HLGEM mais il y en a aussi d'autres importants.

Généralement, votre piste d'audit va voir ses exigences évoluer avec le temps. Vous pouvez finir par vouloir suivre les utilisateurs de la base de données, l'heure du changement, etc. Enfin, vous souhaiterez probablement purger les données de piste d'audit après une période de temps indépendante et une table entièrement séparée.

Bien sûr, il peut y avoir des cas où vous souhaitez les fusionner complètement (comme nous le faisons pour les taux d'imposition dans LedgerSMB) car les données historiques peuvent être utilisées pour les calculs actuels et le nombre d'enregistrements est probablement relativement faible.

Je vais cependant suggérer que le stockage d'objets dans des tableaux comme celui-ci conduit rarement à de bonnes conceptions normalisées. D'après mon expérience, vous voulez vraiment une encapsulation entre un bon stockage normalisé et un modèle objet d'application.

Chris Travers
la source
2
Qu'entendez-vous par «encapsulation entre un bon stockage normalisé et un modèle objet d'application»? Souhaitez-vous exposer cette idée ou donner un exemple?
cubetwo1729