Frage

Wir haben im Projekt die Anforderung, alle Revisionen (Änderungsverlauf) für die Entitäten in der Datenbank zu speichern.Derzeit haben wir hierfür 2 entworfene Vorschläge:

z.B.für die Entität „Mitarbeiter“.

Entwurf 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)"

Entwurf 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 „Design 1“ besteht darin, dass wir XML jedes Mal analysieren müssen, wenn Sie auf Daten zugreifen müssen.Dies wird den Prozess verlangsamen und auch einige Einschränkungen mit sich bringen, z. B. können wir keine Verknüpfungen zu den Revisionsdatenfeldern hinzufügen.

Und das Problem mit „Design 2“ besteht darin, dass wir jedes einzelne Feld auf allen Entitäten duplizieren müssen (wir haben etwa 70–80 Entitäten, für die wir Revisionen beibehalten möchten).

War es hilfreich?

Lösung

  1. Tun nicht Fügen Sie alles in einer Tabelle mit einem IsCurrent-Diskriminatorattribut ein.Dies verursacht auf der ganzen Linie nur Probleme, 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 auch die EmployeeHistories-Tabelle und alle damit verbundenen Sprocs ändern.Verdoppelt möglicherweise Ihren Schemaänderungsaufwand.
  3. Design 1 funktioniert gut und verursacht bei richtiger Ausführung keine großen Leistungseinbußen.Sie könnten ein XML-Schema und sogar Indizes verwenden, um mögliche Leistungsprobleme zu überwinden.Ihr Kommentar zum Parsen der XML-Datei ist gültig, aber Sie können mithilfe von xquery problemlos eine Ansicht erstellen, die Sie in Abfragen einbeziehen und mit der Sie eine Verbindung herstellen 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 

Andere Tipps

Ich denke, die entscheidende Frage, die hier gestellt werden muss, lautet: „Wer/Was wird die Geschichte nutzen?“

Wenn es hauptsächlich um die Berichterstattung bzw. den für Menschen lesbaren Verlauf geht, haben wir dieses Schema in der Vergangenheit implementiert ...

Erstellen Sie eine Tabelle namens „AuditTrail“ oder etwas 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

Anschließend können Sie allen Ihren Tabellen eine Spalte „LastUpdatedByUserID“ hinzufügen, die jedes Mal festgelegt werden sollte, wenn Sie eine Aktualisierung/Einfügung in der Tabelle durchführen.

Anschließend können Sie jeder Tabelle einen Auslöser hinzufügen, um alle auftretenden Einfügungen/Aktualisierungen abzufangen und für jedes geänderte Feld einen Eintrag in dieser Tabelle zu erstellen.Da die Tabelle bei jedem Update/Insert auch mit der „LastUpdateByUserID“ versorgt wird, können Sie im Trigger auf diesen Wert zugreifen und ihn beim Hinzufügen zur Audit-Tabelle 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 sein, aber bei meiner Web-App erhalten wir viel mehr Lese- als Schreibvorgänge, und die Leistung scheint ziemlich gut zu sein.Wir haben sogar ein kleines VB.NET-Dienstprogramm geschrieben, um die Trigger basierend auf den Tabellendefinitionen automatisch zu schreiben.

Nur ein Gedanke!

Der Geschichtstabellen Artikel in der Datenbankprogrammierer Blog könnte nützlich sein – behandelt einige der hier angesprochenen Punkte und diskutiert die Speicherung von Deltas.

Bearbeiten

Im Geschichtstabellen Aufsatz, der Autor (Kenneth Downs), empfiehlt die Pflege einer Verlaufstabelle mit mindestens sieben Spalten:

  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. Unabhängig davon, ob es sich bei der Änderung um eine Einfügung, Aktualisierung oder Löschung handelte,
  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 eine Aufblähung zu vermeiden.Das Speichern des Deltas für numerische Werte kann spätere Abfragen erleichtern, auch wenn es aus den alten und neuen Werten abgeleitet werden kann.

Die Verlaufstabelle muss sicher sein, sodass Nicht-Systembenutzer daran gehindert werden, Zeilen einzufügen, zu aktualisieren oder zu löschen.Um die Gesamtgröße zu reduzieren, sollte nur eine regelmäßige Bereinigung unterstützt werden (sofern der Anwendungsfall dies zulässt).

Wir haben eine Lösung implementiert, die der von Chris Roberts vorgeschlagenen Lösung 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 schließlich 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, und nicht die gesamte Zeile.

Vermeiden Sie Design 1;Dies 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.Z.B.In MySQL können Sie ganz einfach eine Tabelle mit derselben Struktur wie eine andere Tabelle erstellen (create table X like Y).Und wenn Sie die Struktur der Datensatztabelle in Ihrer Live-Datenbank ändern möchten, müssen Sie Folgendes verwenden alter table Befehle sowieso - und es ist kein großer Aufwand, diese Befehle auch für Ihre Verlaufstabelle auszuführen.

Anmerkungen

  • Die Datensatztabelle enthält nur die letzte Revision.
  • Die Verlaufstabelle enthält alle vorherigen Revisionen der Datensätze in der Datensatztabelle.
  • Der Primärschlüssel der Verlaufstabelle ist ein hinzugefügter Primärschlüssel der Datensatztabelle RevisionId Spalte;
  • Denken Sie über zusätzliche Hilfsfelder nach, z ModifiedBy – der Benutzer, der eine bestimmte Revision erstellt hat.Möglicherweise möchten Sie auch ein Feld haben DeletedBy um zu verfolgen, wer eine bestimmte Revision gelöscht hat.
  • Überlegen Sie, was DateModified sollte bedeuten – entweder bedeutet es, 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 „Records“ 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 brauchen).Hängt von Ihnen ab und davon, was Sie tatsächlich aufnehmen möchten.

Operationen in Design 2 sind sehr trivial:

Ändern
  • Kopieren Sie den Datensatz aus der Tabelle „Datensätze“ in die Tabelle „Verlauf“, geben Sie ihm eine neue Revisions-ID (sofern er nicht bereits in der Tabelle „Datensätze“ vorhanden ist), behandeln Sie „DateModified“ (hängt davon ab, wie Sie es interpretieren, siehe Hinweise oben).
  • Fahren Sie mit der normalen Aktualisierung des Datensatzes in der Datensatztabelle fort
Löschen
  • Machen Sie genau das Gleiche wie im ersten Schritt des Änderungsvorgangs.Behandeln Sie DateModified/DateDeleted entsprechend, abhängig von der von Ihnen gewählten Interpretation.
Wiederherstellen (oder Rollback)
  • Nehmen Sie die höchste (oder eine bestimmte?) Revision aus der Verlaufstabelle und kopieren Sie sie in die Datensatztabelle
Revisionsverlauf für einen bestimmten Datensatz auflisten
  • 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, werden alle dafür erforderlichen SQL-Befehle sehr, sehr einfach sein, ebenso wie die Wartung!Vielleicht wird es viel, viel einfacher wenn Sie die Hilfsspalten verwenden (RevisionId, DateModified) auch in der Records-Tabelle – um beide Tabellen exakt in der gleichen Struktur zu halten (außer bei eindeutigen Schlüsseln)!Dies ermöglicht einfache SQL-Befehle, die gegenüber jeder Änderung der Datenstruktur tolerant sind:

insert into EmployeeHistory select * from Employe where ID = XX

Vergessen Sie nicht, Transaktionen zu verwenden!

Was die Skalierung betrifft, Diese Lösung ist sehr effizient, da Sie keine Daten aus XML hin und her transformieren, sondern nur ganze Tabellenzeilen kopieren – sehr einfache Abfragen unter Verwendung von Indizes – sehr effizient!

Wenn Sie den Verlauf speichern müssen, erstellen Sie eine Schattentabelle mit demselben Schema wie die Tabelle, die Sie verfolgen, und einer Spalte „Revisionsdatum“ und „Revisionstyp“ (z. B.„löschen“, „aktualisieren“).Schreiben (oder generieren Sie – siehe unten) eine Reihe von Triggern, 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 dieser Tabelle erstellt.

Versuchen Sie nicht, hierfür XML zu verwenden, da die XML-Speicherung viel weniger effizient ist als die native Datenbanktabellenspeicherung, die diese Art von Trigger verwendet.

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

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

Wo Ist aktiv ist ein Zeichen für die neueste Version

Wenn Sie einige zusätzliche Informationen mit Revisionen in Verbindung bringen möchten, können Sie eine separate Tabelle erstellen, die diese Informationen enthält, und sie mit Entitätstabellen mithilfe der PK FK -Beziehung verknüpfen.

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

  • Einfache Datenbankstruktur
  • Keine Konflikte, da die Tabelle nur noch angehängt werden kann
  • Sie können ein Rollback zur vorherigen Version durchführen, indem Sie einfach das IsActive-Flag ändern
  • Es sind keine Verknüpfungen erforderlich, um den Objektverlauf abzurufen

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

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

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

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

Die Komplexität der Aufrechterhaltung kann durch Ansichten und „anstelle“ von Triggern ausgeblendet werden (in Oracle gehe ich von ähnlichen Dingen in anderen RDBMS aus; 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, es kann jedoch zu komplexen Abfragen kommen.

Persönlich gefällt mir die Art und Weise, wie Sie mit Design 2 vorgehen, sehr gut, so wie ich es in der Vergangenheit auch gemacht habe.Es ist einfach zu verstehen, einfach zu implementieren und einfach zu warten.

Außerdem entsteht nur sehr wenig Overhead für die Datenbank und die Anwendung, insbesondere bei der Durchführung von Leseabfragen, was Sie wahrscheinlich in 99 % der Fälle tun werden.

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

Ich werde Ihnen meinen Entwurf vorstellen. Er unterscheidet sich von Ihren beiden Entwürfen darin, dass für jeden Entitätstyp eine Tabelle erforderlich ist.Ich habe herausgefunden, dass der beste Weg, ein Datenbankdesign zu beschreiben, ERD ist. Hier ist mein Weg:

enter image description here

In diesem Beispiel haben wir eine Entität mit dem Namen Mitarbeiter. Benutzer Die Tabelle enthält die Datensätze Ihrer Benutzer und juristische Person Und Entitätsrevision sind zwei Tabellen, die den Revisionsverlauf für alle Entitätstypen enthalten, die Sie in Ihrem System haben werden.So funktioniert dieses Design:

Die beiden Felder von Entitäts-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, ihre Entitäts-ID bleibt jedoch gleich.Sie müssen diese Entitäts-ID in Ihrer Mitarbeitertabelle behalten (als Fremdschlüssel).Sie sollten auch den Typ Ihrer Entität im speichern juristische Person Tisch (z.B.'Mitarbeiter').Was nun die revision_id betrifft, so verfolgt sie, wie der Name schon sagt, Ihre Entitätsrevisionen.Der beste Weg, den ich dafür gefunden habe, ist die Verwendung von Angestellten ID als Ihre revision_id.Das 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 in Ihrem Fall nicht sicher).Der einzige wichtige Hinweis besteht darin, dass die Kombination aus „entity_id“ und „revision_id“ eindeutig sein sollte.

Es gibt auch ein Zustand Feld innerhalb Entitätsrevision Tabelle, die den Revisionsstand anzeigt.Es kann einen der drei Zustände haben: latest, obsolete oder deleted (Wenn Sie sich nicht auf das Datum der Überarbeitungen verlassen, können Sie Ihre Anfragen erheblich steigern.)

Eine letzte Anmerkung 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 in Zukunft hinzufügen könnten.

EINFÜHRUNG

Für jede Mitarbeiter Zu dem Eintrag, den Sie in die Datenbank einfügen möchten, fügen Sie auch einen Datensatz hinzu juristische Person Und Entitätsrevision.Diese letzten beiden Datensätze helfen Ihnen dabei, den Überblick darüber zu behalten, 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 der Entity_revision.Mit der zweiten können Sie erkennen, von wem und wann der Datensatz aktualisiert wurde.

STREICHUNG

Zum Löschen eines Mitarbeiters wird ein Datensatz mit der Angabe der Löschung in „entity_revision“ eingefügt und fertig.

Wie Sie in diesem Entwurf sehen können, werden niemals Daten geändert oder aus der Datenbank entfernt, und was noch wichtiger ist: Für jeden Entitätstyp ist nur eine Tabelle erforderlich.Ich persönlich finde dieses Design sehr flexibel und einfach zu handhaben.Aber bei Ihnen bin ich mir nicht sicher, da Ihre Bedürfnisse möglicherweise anders sind.

[AKTUALISIEREN]

Da ich in den neuen MySQL-Versionen Partitionen unterstützt habe, glaube ich, dass mein Design auch eine der besten Leistungen bietet.Man kann partitionieren entity Tabelle verwenden type Feld während der Partitionierung entity_revision mit seiner state Feld.Dies wird die steigern SELECT Abfragen bei weitem, während das Design einfach und klar bleibt.

Wenn tatsächlich ein Audit-Trail alles ist, was Sie brauchen, würde ich zur Audit-Tabellenlösung tendieren (komplett mit denormalisierten Kopien der wichtigen Spalte in anderen Tabellen, z. B. UserName).Bedenken Sie jedoch, dass die bittere Erfahrung zeigt, dass eine einzelne Prüftabelle in der Zukunft ein großer Engpass sein wird;Es lohnt sich wahrscheinlich, für alle Ihre geprüften Tabellen individuelle Prüftabellen 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.Um den Zugriff auf aktuelle Werte komfortabel zu gestalten, können Sie eine Ansicht nutzen.Wenn Sie diesen Ansatz wählen, können Probleme auftreten, wenn Ihre versionierten Daten auf veränderliche, aber nicht versionierte Daten verweisen.

Die Überarbeitung von Daten ist ein Aspekt des „gültige Zeit' Konzept einer temporären Datenbank.Hierzu wurde viel geforscht und es sind viele Muster und Richtlinien entstanden.Ich habe eine lange Antwort mit einer Reihe von Verweisen darauf geschrieben Das Frage an Interessierte.

Wenn Sie das erste tun möchten, möchten Sie möglicherweise auch XML für die Mitarbeitertabelle verwenden.Die meisten neueren Datenbanken ermöglichen die Abfrage von XML-Feldern, sodass dies nicht immer ein Problem darstellt.Und es könnte einfacher sein, eine Möglichkeit zu haben, 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önnten dies vereinfachen, indem Sie nur eine Employees-Tabelle mit einem DateModified-Feld haben.Die EmployeeId + DateModified wäre der Primärschlüssel und Sie können eine neue Revision speichern, indem Sie einfach eine Zeile hinzufügen.Auf diese Weise ist auch die Archivierung älterer Versionen und die Wiederherstellung von Versionen aus dem Archiv einfacher.

Eine andere Möglichkeit, dies zu tun, könnte sein Datentresormodell von Dan Linstedt.Ich habe ein Projekt für das niederländische Statistikamt durchgeführt, bei dem dieses Modell verwendet wurde, und es funktioniert recht gut.Aber ich glaube nicht, dass es für den täglichen Datenbankgebrauch direkt nützlich ist.Vielleicht bekommen Sie aber auch beim Lesen seiner Aufsätze einige Anregungen.

Wie wäre es mit:

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

Sie erstellen den Primärschlüssel (EmployeeId, DateModified) und um die „aktuellen“ Datensätze zu erhalten, wählen Sie einfach MAX(DateModified) für jede Mitarbeiter-ID aus.Das Speichern eines IsCurrent ist eine sehr schlechte Idee, da er erstens berechnet werden kann und zweitens viel zu leicht dazu führt, dass die Daten nicht mehr synchron sind.

Sie können auch eine Ansicht erstellen, die nur die neuesten Datensätze auflistet, und diese meist bei der Arbeit in Ihrer App verwenden.Das Schöne an diesem Ansatz ist, dass Sie keine Duplikate von 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. zu erhalten. .

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

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

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

Oder globale Anwendungslösung:

// 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)"

Wir hatten ähnliche Anforderungen und stellten fest, dass der Benutzer es oft einfach nur wollte sehen Was geändert wurde, macht nicht unbedingt alle Änderungen rückgängig.

Ich bin nicht sicher, was Ihr Anwendungsfall ist, aber wir haben eine Prüftabelle erstellt, die automatisch mit Änderungen an einer Geschäftsentität aktualisiert wird, einschließlich des Anzeigenamens aller Fremdschlüsselverweise 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 (alles geschieht in einer einzigen Datenbanktransaktion, falls es Probleme gibt).

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

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 Hauptstraße“, dann eine weitere ID 3, „Bob“, „234 Elm St“ und so weiter, im Wesentlichen die Möglichkeit, einen Revisionsverlauf herauszugeben, der jede Adresse zeigt, an der „Bob“ war .

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

Bei Einfügungen muss dann „ist aktuell“ festgelegt und auch „ist aktuell“ im vorherigen „ist aktuell“-Datensatz deaktiviert werden.Bei Abfragen muss „ist aktuell“ angegeben werden, es sei denn, Sie möchten den gesamten Verlauf.

Wenn es sich um eine sehr große Tabelle handelt oder eine große Anzahl von Überarbeitungen erwartet wird, müssen noch weitere Optimierungen vorgenommen werden. Dabei handelt es sich jedoch um einen ziemlich standardmäßigen Ansatz.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top