Datenbankdesign für Revisionen?


125

Im Projekt müssen alle Revisionen (Änderungsverlauf) für die Entitäten in der Datenbank gespeichert werden. Derzeit haben wir 2 Vorschläge dafür:

zB für "Mitarbeiter"

Design 1:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"

Design 2:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- In this approach we have basically duplicated all the fields on Employees 
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName, 
      LastName, DepartmentId, .., ..)"

Gibt es eine andere Möglichkeit, dies zu tun?

Das Problem mit dem "Design 1" ist, dass wir XML jedes Mal analysieren müssen, wenn Sie auf Daten zugreifen müssen. Dies verlangsamt den Prozess und fügt einige Einschränkungen hinzu, da wir keine Verknüpfungen zu den Revisionsdatenfeldern hinzufügen können.

Und das Problem mit dem "Design 2" ist, dass wir jedes Feld auf allen Entitäten duplizieren müssen (wir haben ungefähr 70-80 Entitäten, für die wir Revisionen beibehalten möchten).



1
Zu Ihrer Information: Nur für den Fall, dass es helfen kann. SQL Server 2008 und höher verfügt über eine Technologie, die den Verlauf der Änderungen in der Tabelle anzeigt. Besuchen Sie simple-talk.com/sql/learn-sql-server/… , um mehr zu erfahren, und ich bin sicher, dass DBs wie Oracle wird auch so etwas haben.
Durai Amuthan.H

Beachten Sie, dass einige Spalten XML oder JSON selbst speichern können. Wenn dies jetzt nicht der Fall ist, könnte es in Zukunft passieren. Stellen Sie besser sicher, dass Sie solche Daten nicht ineinander verschachteln müssen.
Jakubiszon

Antworten:


38
  1. Fügen Sie nicht alles in eine Tabelle mit einem IsCurrent-Diskriminatorattribut ein. Dies verursacht nur Probleme auf der ganzen Linie, erfordert Ersatzschlüssel und alle möglichen anderen Probleme.
  2. Design 2 hat Probleme mit Schemaänderungen. Wenn Sie die Employees-Tabelle ändern, müssen Sie die EmployeeHistories-Tabelle und alle dazugehörigen Sprocs ändern. Verdoppelt möglicherweise Ihren Aufwand für Schemaänderungen.
  3. Design 1 funktioniert gut und kostet bei richtiger Ausführung nicht viel an Leistung. Sie können ein XML-Schema und sogar Indizes verwenden, um mögliche Leistungsprobleme zu überwinden. Ihr Kommentar zum Parsen der XML ist gültig, aber Sie können mit xquery problemlos eine Ansicht erstellen, die Sie in Abfragen aufnehmen und der Sie beitreten können. Etwas wie das...
CREATE VIEW EmployeeHistory
AS
, FirstName, , DepartmentId

SELECT EmployeeId, RevisionXML.value('(/employee/FirstName)[1]', 'varchar(50)') AS FirstName,

  RevisionXML.value('(/employee/LastName)[1]', 'varchar(100)') AS LastName,

  RevisionXML.value('(/employee/DepartmentId)[1]', 'integer') AS DepartmentId,

FROM EmployeeHistories 

25
Warum sagen Sie, dass Sie mit dem IsCurrent-Trigger nicht alles in einer Tabelle speichern sollen? Könnten Sie mich auf einige Beispiele hinweisen, bei denen dies problematisch wäre?
Nathan W

@ Simon Munro Was ist mit einem Primärschlüssel oder einem Clusterschlüssel? Welchen Schlüssel können wir in die Verlaufstabelle von Design 1 einfügen, um die Suche zu beschleunigen?
Gotqn

Ich gehe von einem einfachen SELECT * FROM EmployeeHistory WHERE LastName = 'Doe'Ergebnis in einem vollständigen Tabellenscan aus . Nicht die beste Idee, eine Anwendung zu skalieren.
Kaii

54

Ich denke, die Schlüsselfrage, die hier gestellt werden muss, ist: Wer / Was wird die Geschichte verwenden?

Wenn es hauptsächlich um Berichterstattung / lesbare Historie geht, haben wir dieses Schema in der Vergangenheit implementiert ...

Erstellen Sie eine Tabelle mit dem Namen 'AuditTrail' oder eine Tabelle mit den folgenden Feldern ...

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[OldValue] [varchar](5000) NULL,
[NewValue] [varchar](5000) NULL

Sie können dann allen Ihren Tabellen eine Spalte 'LastUpdatedByUserID' hinzufügen, die jedes Mal festgelegt werden sollte, wenn Sie die Tabelle aktualisieren / einfügen.

Sie können dann jeder Tabelle einen Trigger hinzufügen, um alle Einfügungen / Aktualisierungen abzufangen und für jedes geänderte Feld einen Eintrag in dieser Tabelle zu erstellen. Da die Tabelle auch mit der 'LastUpdateByUserID' für jede Aktualisierung / Einfügung geliefert wird, können Sie auf diesen Wert im Trigger zugreifen und ihn beim Hinzufügen zur Überwachungstabelle verwenden.

Wir verwenden das RecordID-Feld, um den Wert des Schlüsselfelds der zu aktualisierenden Tabelle zu speichern. Wenn es sich um einen kombinierten Schlüssel handelt, führen wir einfach eine Zeichenfolgenverkettung mit einem '~' zwischen den Feldern durch.

Ich bin mir sicher, dass dieses System Nachteile haben kann - bei stark aktualisierten Datenbanken kann die Leistung beeinträchtigt werden, aber bei meiner Web-App erhalten wir viel mehr Lese- als Schreibvorgänge und es scheint ziemlich gut zu funktionieren. Wir haben sogar ein kleines VB.NET-Dienstprogramm geschrieben, um die Trigger basierend auf den Tabellendefinitionen automatisch zu schreiben.

Nur ein Gedanke!


5
Der NewValue muss nicht gespeichert werden, da er in der überwachten Tabelle gespeichert ist.
Petrus Theron

17
Genau genommen stimmt das. Wenn jedoch über einen bestimmten Zeitraum mehrere Änderungen am selben Feld vorgenommen werden, erleichtert das Speichern des neuen Werts Abfragen wie "Alle von Brian vorgenommenen Änderungen anzeigen" erheblich, da alle Informationen zu einem Update gespeichert sind ein Datensatz. Nur ein Gedanke!
Chris Roberts

1
Ich denke, sysnamemöglicherweise ist ein geeigneterer Datentyp für die Tabellen- und Spaltennamen.
Sam

2
@Sam mit sysname fügt keinen Wert hinzu; es könnte sogar verwirrend sein ... stackoverflow.com/questions/5720212/…
Jowen

19

Die Geschichte Tabellen Artikel in der Datenbank - Programmierer Blog nützlich sein könnten - deckt einige der Punkte hier angehoben und diskutiert die Speicherung von Deltas.

Bearbeiten

Im Aufsatz " Geschichtstabellen" empfiehlt der Autor ( Kenneth Downs ), eine Geschichtstabelle mit mindestens sieben Spalten zu führen:

  1. Zeitstempel der Änderung,
  2. Benutzer, der die Änderung vorgenommen hat,
  3. Ein Token zur Identifizierung des geänderten Datensatzes (wobei der Verlauf getrennt vom aktuellen Status verwaltet wird).
  4. Ob die Änderung ein Einfügen, Aktualisieren oder Löschen war,
  5. Der alte Wert,
  6. Der neue Wert,
  7. Das Delta (für Änderungen an numerischen Werten).

Spalten, die sich nie ändern oder deren Verlauf nicht erforderlich ist, sollten nicht in der Verlaufstabelle nachverfolgt werden, um ein Aufblähen zu vermeiden. Das Speichern des Deltas für numerische Werte kann nachfolgende Abfragen erleichtern, obwohl es aus den alten und neuen Werten abgeleitet werden kann.

Die Verlaufstabelle muss sicher sein, und Nicht-Systembenutzer dürfen keine Zeilen einfügen, aktualisieren oder löschen. Es sollte nur eine regelmäßige Spülung unterstützt werden, um die Gesamtgröße zu verringern (und wenn dies im Anwendungsfall zulässig ist).


14

Wir haben eine Lösung implementiert, die der von Chris Roberts vorgeschlagenen sehr ähnlich ist und die für uns ziemlich gut funktioniert.

Der einzige Unterschied besteht darin, dass wir nur den neuen Wert speichern. Der alte Wert wird immerhin in der vorherigen Verlaufszeile gespeichert

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[NewValue] [varchar](5000) NULL

Nehmen wir an, Sie haben eine Tabelle mit 20 Spalten. Auf diese Weise müssen Sie nur die genaue Spalte speichern, die sich geändert hat, anstatt die gesamte Zeile zu speichern.


14

Vermeiden Sie Design 1; Es ist nicht sehr praktisch, wenn Sie beispielsweise ein Rollback auf alte Versionen der Datensätze durchführen müssen - entweder automatisch oder "manuell" über die Administratorkonsole.

Ich sehe keine wirklichen Nachteile von Design 2. Ich denke, die zweite Tabelle "Verlauf" sollte alle Spalten enthalten, die in der ersten Tabelle "Datensätze" vorhanden sind. Zum Beispiel können Sie in MySQL einfach eine Tabelle mit derselben Struktur wie eine andere Tabelle ( create table X like Y) erstellen . Und wenn Sie sich jetzt ändern Struktur der Datensätze Tabelle in Ihrer Live - Datenbank sind, haben Sie zu verwenden alter tableBefehle sowieso - und es gibt keine große Mühe , diese Befehle auch für Ihre History - Tabelle in Laufen.

Anmerkungen

  • Die Datensatztabelle enthält nur die letzte Revision.
  • Die Verlaufstabelle enthält alle vorherigen Überarbeitungen von Datensätzen in der Datensatztabelle.
  • Der Primärschlüssel der Verlaufstabelle ist ein Primärschlüssel der Datensatztabelle mit hinzugefügter RevisionIdSpalte.
  • Denken Sie an zusätzliche Hilfsfelder wie ModifiedBy- den Benutzer, der eine bestimmte Revision erstellt hat. Möglicherweise möchten Sie auch ein Feld haben, um DeletedByzu verfolgen, wer eine bestimmte Revision gelöscht hat.
  • Überlegen Sie, was DateModifiedbedeuten soll - entweder bedeutet dies, wo diese bestimmte Revision erstellt wurde, oder es bedeutet, wann diese bestimmte Revision durch eine andere ersetzt wurde. Ersteres erfordert, dass sich das Feld in der Tabelle "Datensätze" befindet, und scheint auf den ersten Blick intuitiver zu sein. Die zweite Lösung scheint jedoch für gelöschte Datensätze praktischer zu sein (Datum, an dem diese bestimmte Revision gelöscht wurde). Wenn Sie sich für die erste Lösung entscheiden, benötigen Sie wahrscheinlich ein zweites Feld DateDeleted(natürlich nur, wenn Sie es benötigen). Kommt auf dich an und was du eigentlich aufnehmen willst.

Operationen in Design 2 sind sehr trivial:

Ändern
  • Kopieren Sie den Datensatz aus der Datensatztabelle in die Verlaufstabelle, geben Sie ihm eine neue Revisions-ID (falls er nicht bereits in der Datensatztabelle vorhanden ist) und behandeln Sie DateModified (abhängig von Ihrer Interpretation, siehe Anmerkungen oben).
  • Fahren Sie mit der normalen Aktualisierung des Datensatzes in der Datensatztabelle fort
Löschen
  • Gehen Sie genauso vor wie im ersten Schritt des Änderungsvorgangs. Behandeln Sie DateModified / DateDeleted entsprechend der von Ihnen gewählten Interpretation entsprechend.
Wiederherstellen (oder Rollback)
  • Nehmen Sie die höchste (oder eine bestimmte?) Revision aus der Verlaufstabelle und kopieren Sie sie in die Datensatztabelle
Listen Sie den Revisionsverlauf für einen bestimmten Datensatz auf
  • Wählen Sie aus der Verlaufstabelle und der Datensatztabelle
  • Überlegen Sie, was genau Sie von dieser Operation erwarten. Es wird wahrscheinlich bestimmen, welche Informationen Sie aus den Feldern DateModified / DateDeleted benötigen (siehe Hinweise oben).

Wenn Sie sich für Design 2 entscheiden, sind alle dafür erforderlichen SQL-Befehle sehr einfach und wartungsfreundlich! Vielleicht ist es viel einfacher, wenn Sie die Hilfsspalten ( RevisionId, DateModified) auch in der Records-Tabelle verwenden - um beide Tabellen in genau derselben Struktur zu halten (mit Ausnahme eindeutiger Schlüssel)! Dies ermöglicht einfache SQL-Befehle, die gegenüber Änderungen der Datenstruktur tolerant sind:

insert into EmployeeHistory select * from Employe where ID = XX

Vergessen Sie nicht, Transaktionen zu verwenden!

Die Skalierung ist sehr effizient, da Sie keine Daten aus XML hin und her transformieren, sondern nur ganze Tabellenzeilen kopieren - sehr einfache Abfragen, Indizes verwenden - sehr effizient!


12

Wenn Sie den Verlauf speichern müssen, erstellen Sie eine Schattentabelle mit demselben Schema wie die Tabelle, die Sie verfolgen, und einer Spalte mit dem Überarbeitungsdatum und dem Überarbeitungstyp (z. B. "Löschen", "Aktualisieren"). Schreiben Sie eine Reihe von Triggern (oder generieren Sie sie - siehe unten), um die Prüftabelle zu füllen.

Es ist ziemlich einfach, ein Tool zu erstellen, das das Systemdatenwörterbuch für eine Tabelle liest und ein Skript generiert, das die Schattentabelle und eine Reihe von Triggern zum Auffüllen erstellt.

Versuchen Sie nicht, XML dafür zu verwenden. Der XML-Speicher ist viel weniger effizient als der native Datenbanktabellenspeicher, den diese Art von Trigger verwendet.


3
+1 der Einfachheit halber! Einige werden aus Angst vor späteren Änderungen überentwickeln, während die meiste Zeit tatsächlich keine Änderungen auftreten! Darüber hinaus ist es viel einfacher, die Historien in einer Tabelle und die tatsächlichen Datensätze in einer anderen zu verwalten, als sie alle in einer Tabelle (Albtraum) mit einem Flag oder Status zu haben. Es heißt "KISS" und wird Sie normalerweise langfristig belohnen.
Jeach

+1 stimme vollkommen zu, genau das, was ich in meiner Antwort sage ! Einfach und kraftvoll!
TMS

8

Ramesh, ich war an der Entwicklung eines Systems beteiligt, das auf dem ersten Ansatz basiert.
Es stellte sich heraus, dass das Speichern von Revisionen als XML zu einem enormen Datenbankwachstum führt und die Dinge erheblich verlangsamt.
Mein Ansatz wäre, eine Tabelle pro Entität zu haben:

Employee (Id, Name, ... , IsActive)  

Dabei ist IsActive ein Zeichen für die neueste Version

Wenn Sie Revisionen zusätzliche Informationen zuordnen möchten, können Sie eine separate Tabelle mit diesen Informationen erstellen und diese mithilfe der PK \ FK-Beziehung mit Entitätstabellen verknüpfen.

Auf diese Weise können Sie alle Versionen von Mitarbeitern in einer Tabelle speichern. Vorteile dieses Ansatzes:

  • Einfache Datenbankstruktur
  • Keine Konflikte, da die Tabelle nur noch angehängt wird
  • Sie können auf die vorherige Version zurücksetzen, indem Sie einfach das IsActive-Flag ändern
  • Joins sind nicht erforderlich, um den Objektverlauf abzurufen

Beachten Sie, dass Sie zulassen sollten, dass der Primärschlüssel nicht eindeutig ist.


6
Ich würde eine Spalte "RevisionNumber" oder "RevisionDate" anstelle von oder zusätzlich zu IsActive verwenden, damit Sie alle Revisionen in der richtigen Reihenfolge sehen können.
Sklivvz

Ich würde eine "parentRowId" verwenden, da dies Ihnen einen einfachen Zugriff auf die vorherigen Versionen sowie die Möglichkeit bietet, sowohl die Basis als auch das Ende schnell zu finden.
Chacham15

6

Die Art und Weise, wie ich das in der Vergangenheit gesehen habe, ist

Employees (EmployeeId, DateModified, < Employee Fields > , boolean isCurrent );

Sie "aktualisieren" diese Tabelle nie (außer um die Gültigkeit von isCurrent zu ändern), fügen Sie einfach neue Zeilen ein. Für eine bestimmte EmployeeId kann nur 1 Zeile isCurrent == 1 haben.

Die Komplexität der Verwaltung kann durch Ansichten und "anstelle von" Triggern verborgen werden (in Oracle nehme ich ähnliche Dinge an wie in anderen RDBMS). Sie können sogar zu materialisierten Ansichten wechseln, wenn die Tabellen zu groß sind und nicht von Indizes verarbeitet werden können. .

Diese Methode ist in Ordnung, aber Sie können mit einigen komplexen Abfragen enden.

Persönlich mag ich Ihre Design 2-Methode ziemlich gern, so wie ich es auch in der Vergangenheit gemacht habe. Es ist einfach zu verstehen, einfach zu implementieren und einfach zu warten.

Es verursacht auch sehr wenig Overhead für die Datenbank und die Anwendung, insbesondere wenn Leseabfragen ausgeführt werden, was wahrscheinlich in 99% der Fälle der Fall ist.

Es wäre auch recht einfach, die Erstellung der zu verwaltenden Verlaufstabellen und Trigger automatisch durchzuführen (vorausgesetzt, dies würde über Trigger erfolgen).


4

Revisionen von Daten sind ein Aspekt des " Gültigkeitszeit " -Konzepts einer zeitlichen Datenbank. Es wurde viel geforscht, und es sind viele Muster und Richtlinien entstanden. Ich habe eine lange Antwort mit einer Reihe von Verweisen auf diese Frage für Interessierte geschrieben.


4

Ich werde mein Design mit Ihnen teilen und es unterscheidet sich von Ihren beiden Designs darin, dass es eine Tabelle pro Entitätstyp erfordert. Ich habe festgestellt, dass der beste Weg, ein Datenbankdesign zu beschreiben, die ERD ist. Hier ist meine:

Geben Sie hier die Bildbeschreibung ein

In diesem Beispiel haben wir eine Entität namens Mitarbeiter . Die Benutzertabelle enthält die Datensätze Ihrer Benutzer, und entity und entity_revision sind zwei Tabellen, die den Revisionsverlauf für alle Entitätstypen enthalten, die in Ihrem System vorhanden sind. So funktioniert dieses Design:

Die beiden Felder entity_id und revision_id

Jede Entität in Ihrem System verfügt über eine eigene eindeutige Entitäts-ID. Ihre Entität wird möglicherweise überarbeitet, aber ihre entity_id bleibt gleich. Sie müssen diese Entitäts-ID in Ihrer Mitarbeitertabelle behalten (als Fremdschlüssel). Sie sollten auch den Typ Ihrer Entität in der Entitätstabelle speichern (z. B. 'Mitarbeiter'). Wie der Name schon sagt, verfolgt die revision_id die Entitätsrevisionen. Der beste Weg, den ich dafür gefunden habe, ist, die employee_id als Ihre revision_id zu verwenden. Dies bedeutet, dass Sie doppelte Revisions-IDs für verschiedene Arten von Entitäten haben, aber das ist für mich kein Vergnügen (ich bin mir über Ihren Fall nicht sicher). Der einzige wichtige Hinweis ist, dass die Kombination von entity_id und revision_id eindeutig sein sollte.

Es gibt auch einen Zustand Feld innerhalb entity_revision Tabelle , die den Zustand der Revision angezeigt. Es kann einen der drei Zustände haben : latest, obsoleteoder deleted( wenn Sie sich nicht auf das Datum der Überarbeitung verlassen, können Sie Ihre Abfragen erheblich verbessern).

Ein letzter Hinweis zu revision_id: Ich habe keinen Fremdschlüssel erstellt, der employee_id mit revision_id verbindet, da wir die Tabelle entity_revision nicht für jeden Entitätstyp ändern möchten, den wir möglicherweise in Zukunft hinzufügen.

EINSATZ

Für jeden Mitarbeiter , den Sie in die Datenbank einfügen möchten, fügen Sie der Entität und der Entitätsrevision einen Datensatz hinzu . Mithilfe dieser beiden letzten Datensätze können Sie verfolgen, von wem und wann ein Datensatz in die Datenbank eingefügt wurde.

AKTUALISIEREN

Jede Aktualisierung für einen vorhandenen Mitarbeiterdatensatz wird als zwei Einfügungen implementiert, eine in der Mitarbeitertabelle und eine in entity_revision. Der zweite hilft Ihnen zu wissen, von wem und wann der Datensatz aktualisiert wurde.

STREICHUNG

Zum Löschen eines Mitarbeiters wird in entity_revision ein Datensatz eingefügt, der das Löschen angibt und abgeschlossen ist.

Wie Sie in diesem Entwurf sehen können, werden niemals Daten geändert oder aus der Datenbank entfernt, und was noch wichtiger ist, jeder Entitätstyp erfordert nur eine Tabelle. Persönlich finde ich dieses Design sehr flexibel und einfach zu bearbeiten. Aber ich bin mir nicht sicher, da Ihre Bedürfnisse unterschiedlich sein könnten.

[AKTUALISIEREN]

Nachdem ich Partitionen in den neuen MySQL-Versionen unterstützt habe, glaube ich, dass mein Design auch eine der besten Leistungen bietet. Man kann eine entityTabelle mit einem typeFeld partitionieren entity_revision, während man eine Partition mit seinem stateFeld erstellt. Dies wird die SELECTAbfragen bei weitem beschleunigen und gleichzeitig das Design einfach und sauber halten.


3

Wenn Sie in der Tat nur einen Prüfpfad benötigen, würde ich mich auf die Prüftabellenlösung stützen (einschließlich denormalisierter Kopien der wichtigen Spalte in anderen Tabellen, z UserName. B. ). Beachten Sie jedoch, dass diese bittere Erfahrung darauf hindeutet, dass eine einzelne Prüftabelle später einen großen Engpass darstellen wird. Es lohnt sich wahrscheinlich, individuelle Prüftabellen für alle geprüften Tabellen zu erstellen.

Wenn Sie die tatsächlichen historischen (und / oder zukünftigen) Versionen verfolgen müssen, besteht die Standardlösung darin, dieselbe Entität mit mehreren Zeilen unter Verwendung einer Kombination aus Start-, End- und Dauerwerten zu verfolgen. Sie können eine Ansicht verwenden, um den Zugriff auf aktuelle Werte zu vereinfachen. Wenn Sie diesen Ansatz wählen, können Probleme auftreten, wenn Ihre versionierten Daten auf veränderbare, aber nicht versionierte Daten verweisen.


3

Wenn Sie den ersten ausführen möchten, möchten Sie möglicherweise auch XML für die Employees-Tabelle verwenden. In den meisten neueren Datenbanken können Sie XML-Felder abfragen, sodass dies nicht immer ein Problem darstellt. Und es ist möglicherweise einfacher, auf eine Weise auf Mitarbeiterdaten zuzugreifen, unabhängig davon, ob es sich um die neueste oder eine frühere Version handelt.

Ich würde jedoch den zweiten Ansatz versuchen. Sie können dies vereinfachen, indem Sie nur eine Employees-Tabelle mit einem DateModified-Feld haben. Die EmployeeId + DateModified ist der Primärschlüssel, und Sie können eine neue Revision speichern, indem Sie einfach eine Zeile hinzufügen. Auf diese Weise ist es auch einfacher, ältere Versionen zu archivieren und Versionen aus dem Archiv wiederherzustellen.

Ein anderer Weg, dies zu tun, könnte das Datavault-Modell von Dan Linstedt sein. Ich habe ein Projekt für das niederländische Statistikamt durchgeführt, das dieses Modell verwendet hat, und es funktioniert recht gut. Aber ich denke nicht, dass es direkt für die tägliche Datenbanknutzung nützlich ist. Sie könnten jedoch einige Ideen bekommen, wenn Sie seine Papiere lesen.


2

Wie wäre es mit:

  • Angestellten ID
  • Datum geändert
    • und / oder Revisionsnummer, je nachdem, wie Sie sie verfolgen möchten
  • ModifiedByUSerId
    • sowie alle anderen Informationen, die Sie verfolgen möchten
  • Mitarbeiterfelder

Sie erstellen den Primärschlüssel (EmployeeId, DateModified) und wählen zum Abrufen der "aktuellen" Datensätze einfach MAX (DateModified) für jede Mitarbeiter-ID aus. Das Speichern eines IsCurrent ist eine sehr schlechte Idee, da es erstens berechnet werden kann und zweitens viel zu einfach ist, dass Daten nicht mehr synchron sind.

Sie können auch eine Ansicht erstellen, in der nur die neuesten Datensätze aufgelistet sind, und diese meistens während der Arbeit in Ihrer App verwenden. Das Schöne an diesem Ansatz ist, dass Sie keine doppelten Daten haben und keine Daten von zwei verschiedenen Orten sammeln müssen (aktuell in Employees und archiviert in EmployeesHistory), um den gesamten Verlauf oder Rollback usw. abzurufen. .


Ein Nachteil dieses Ansatzes besteht darin, dass die Tabelle schneller wächst als bei Verwendung von zwei Tabellen.
CDMckay

2

Wenn Sie sich (aus Berichtsgründen) auf Verlaufsdaten verlassen möchten, sollten Sie eine Struktur wie die folgende verwenden:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds the Employee revisions in rows.
"EmployeeHistories (HistoryId, EmployeeId, DateModified, OldValue, NewValue, FieldName)"

Oder globale Lösung für die Anwendung:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds all entities revisions in rows.
"EntityChanges (EntityName, EntityId, DateModified, OldValue, NewValue, FieldName)"

Sie können Ihre Revisionen auch in XML speichern, dann haben Sie nur einen Datensatz für eine Revision. Das wird so aussehen:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds all entities revisions in rows.
"EntityChanges (EntityName, EntityId, DateModified, XMLChanges)"

1
Besser: Event Sourcing verwenden :)
Dariol

1

Wir haben ähnliche Anforderungen hatte, und was wir fanden war , dass oft der Benutzer will nur sehen , was sich geändert hat, nicht unbedingt rollen alle Änderungen zurück.

Ich bin nicht sicher, was Ihr Anwendungsfall ist, aber wir haben eine Tabelle erstellt und geprüft, die automatisch mit Änderungen an einer Geschäftseinheit aktualisiert wird, einschließlich des Anzeigenamens aller Fremdschlüsselreferenzen und -aufzählungen.

Immer wenn der Benutzer seine Änderungen speichert, laden wir das alte Objekt neu, führen einen Vergleich durch, zeichnen die Änderungen auf und speichern die Entität (alle werden in einer einzigen Datenbanktransaktion durchgeführt, falls es Probleme gibt).

Dies scheint für unsere Benutzer sehr gut zu funktionieren und erspart uns die Kopfschmerzen, eine vollständig separate Prüftabelle mit denselben Feldern wie unsere Geschäftseinheit zu haben.


0

Es hört sich so an, als ob Sie Änderungen an bestimmten Entitäten im Laufe der Zeit verfolgen möchten, z. B. ID 3, "Bob", "123 Main Street", dann eine andere ID 3, "Bob", "234 Elm St" usw., die im Wesentlichen in der Lage sind einen Revisionsverlauf herauszuholen, der zeigt, an welcher Adresse "bob" war.

Der beste Weg, dies zu tun, besteht darin, in jedem Datensatz ein Feld "Ist aktuell" und (wahrscheinlich) einen Zeitstempel oder eine FK für eine Datums- / Zeittabelle zu haben.

Die Einfügungen müssen dann das "Ist aktuell" setzen und auch das "Ist aktuell" im vorherigen "Ist aktuell" -Datensatz deaktivieren. Abfragen müssen "Ist aktuell" angeben, es sei denn, Sie möchten den gesamten Verlauf.

Es gibt weitere Verbesserungen, wenn es sich um eine sehr große Tabelle handelt oder eine große Anzahl von Revisionen erwartet wird, aber dies ist ein ziemlich Standardansatz.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.