Geänderte Daten archivieren

Wenn Sie ein Abonnement des Magazins 'Access im Unternehmen' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

Geänderte Daten archivieren

Das Ändern von Daten ist in manchen Fällen an der Tagesordnung – zum Beispiel bei Kundendaten. Wenn Sie in einer Datenbank Kunden verwalten, die nicht nur für einen einzigen Auftrag oder eine einzige Bestellung angelegt werden, sondern mit denen Sie wiederkehrend geschäftlichen Kontakt haben, müssen Sie die Adressdaten auf dem aktuellen Stand halten. Kein Problem: Die entsprechenden Felder lassen sich ja schnell mal überschreiben. Das Problem ist nur, dass hier auch Fehler geschehen. Wenn Sie beispielsweise beim falschen Michael Müller die Lieferadresse ändern, freut sich ein Michael Müller über eine Lieferung, aber der andere, der nichts bestellt hat, wird die Rechnung wohl nicht bezahlen. Und wenn sich solche Fehler nur schwer verhindern lassen, so sollen Sie zumindest die Adressdaten schnell wiederherstellen können ...

Und genau darum kümmert sich der vorliegende Beitrag: Wir entwickeln hier eine automatische Lösung zum Archivieren von geänderten oder gelöschten Datensätzen einer Kundendatenbank. Außerdem wollen wir ein Formular bereitstellen, mit dem Sie den aktuellen und die bereits archivierten Datensätze zu einem Kunden einsehen, vergleichen und beliebige Versionsstände wiederherstellen können.

Voraussetzung

Die Sache hat einen kleinen Haken: Die Lösung basiert auf der Verwendung von Access 2010 oder höher. Der Grund ist, dass wir hier mit den Tabellenereignissen arbeiten, die erst mit Access 2010 eingeführt wurden. Tabellenereignisse sind das Access-Pendant etwa zu den Triggern beim SQL Server. Dabei handelt es sich um ereignisgesteuerte, benutzerdefinierte Aktionen, die durch das Anlegen, Bearbeiten oder Löschen von Daten ausgelöst werden.

In unserem Fall spielt das Anlegen keine Rolle: Wir wollen nur die Möglichkeit liefern, geänderte oder gelöschte Daten wiederherzustellen. Dazu benötigen wir kein Anlegedatum oder ähnliche Informationen. Also werden wir später nur ein Ereignis definieren, das beim Ändern eines Datensatzes der betroffenen Tabelle ausgelöst wird sowie eines für das Löschen eines Datensatzes der Tabelle.

Beispieltabelle

Als Beispieltabelle verwenden wir eine einfache Kundentabelle. Diese sieht im Entwurf wie in Bild 1 aus und enthält nur die notwendigsten Felder.

Tabelle, deren Daten beim Ändern archiviert werden sollen

Bild 1: Tabelle, deren Daten beim Ändern archiviert werden sollen

Zieltabelle der Archivierung

Das Ziel der Archivierung ist eine Tabelle, die fast genauso wie die Originaltabelle aufgebaut ist. Es gibt allerdings einige kleine Unterschiede: Die Zieltabelle verwendet das Feld KundeID nämlich nicht als Primärschlüsselfeld mit dem Felddatentyp Autowert, sondern als einfaches Feld mit dem Typ Zahl. Als Primärschlüsselfeld definieren wir ein weiteres Feld namens ArchivKundeID. Dies legen wir wiederum als Autowertfeld aus (s. Bild 2).

Tabelle zum Archivieren von Daten vor dem Ändern oder Löschen

Bild 2: Tabelle zum Archivieren von Daten vor dem Ändern oder Löschen

Außerdem finden Sie am Ende des Entwurfs der Tabelle zwei weitere Felder namens GeaendertAm und Geloescht­Am mit dem Datentyp Datum/Uhrzeit. Wenn der Benutzer einen Datensatz der Tabelle tblKunden ändert, soll die Version des Datensatzes vor der Änderung in die Tabelle tblKundenArchiv kopiert werden und das aktuelle Datum im Feld GeaendertAm erhalten. Wenn der Benutzer hingegen einen Datensatz löscht, landet dieser zwar auch komplett in der Tabelle tblKundenArchiv – diesmal allerdings mit dem aktuellen Datum im Feld GeloeschtAm.

Makro zum Archivieren eines geänderten Datensatzes

Damit ein geänderter Datensatz vor der Änderung in der Archivtabelle landet, legen Sie ein Makro für das Ereignis Nach Aktualisierung der Tabelle an. Dies erledigen Sie durch einen Mausklick auf den Ribbon-Eintrag Tabelle|Nachfol­ge­er­eignisse|Nach Aktualisierung (s. Bild 3), während die Tabelle in der Datenblattansicht geöffnet ist.

Anlegen des Makros für das Ereignis Nach Aktualisierung der Tabelle ausgelöst

Bild 3: Anlegen des Makros für das Ereignis Nach Aktualisierung der Tabelle ausgelöst

In der Entwurfsansicht legen Sie das entsprechende Makro über den Ribbon-Eintrag Entwurf|Feld-, Datensatz- und Ta­bel­len­er­eig­nis­se|Da­ten­mak­ros erstellen|Nach Ak­tu­alisierung an.

Daraufhin erscheint ein leeres Makro, das Sie nun mit Leben füllen müssen. Das gelingt relativ schnell: Als Erstes wählen Sie mit dem Kombinationsfeld Neue Aktion hinzufügen den Eintrag Datensatz erstellen in aus und tragen als ersten Parameter den Namen der Zieltabelle ein, also tblKundenArchiv. Nun müssen Sie festlegen, wie die einzelnen Felder dieser Tabelle gefüllt werden – und womit. Die benötigte Makroaktion ist schnell gefunden: Sie heißt FestlegenFeld und kann nur angelegt werden, wenn Sie die Makroaktion Datensatz erstellen in markieren und dann das zu dieser Makroaktion gehörende Kombinationsfeld Neue Aktion hinzufügen aufklappen.

Haben Sie diese Aktion hinzugefügt, geben Sie die beiden Parameter ein. Wir beginnen mit dem Feld KundeID, welches in das gleichnamige Feld der Tabelle tblKundenArchiv eingefügt werden soll. Dazu tragen Sie für den Parameter Name einfach den Feldnamen KundeID ein. Das Makro weiß nun automatisch, dass das Feld KundeID der Tabelle tblKundenArchiv gefüllt werden soll. Aber woher beziehen wir den Inhalt für das Feld KundeID? Wenn Sie dort ebenfalls den Eintrag KundeID hinzufügen, greift Access auf den Inhalt des Feldes des geänderten Datensatzes zu. Wenn Sie auf den Feldinhalt des Datensatzes vor der Änderung zugreifen möchten, müssen Sie auf die generische Tabelle namens Alt zugreifen. Für den vorherigen Wert des Feldes KundeID verwenden Sie also etwa den Ausdruck [Alt].[KundeID]. Gut: Beim Feld Kunde­ID hätten Sie auch auf den Wert des geänderten Datensatzes zugreifen können, denn das Primärschlüsselfeld wird ja in der Regel nicht geändert – schon gar nicht, wenn es als Autowert definiert ist. Aber bei den anderen, tendenziell änderbaren Feldern, macht der Zugriff auf die mit Alt referenzierten vorherigen Werte durchaus Sinn. Dementsprechend fügen Sie dem Makro weitere FestlegenFeld-Aktionen hinzu, welche Feld für Feld die Inhalte der Tabelle Alt in die Tabelle tblKundenArchiv überträgt.

Schließlich fehlt noch die Füllung für das Feld GeaendertAm. Hier tragen Sie einfach die Funktion Jetzt() ein. Das komplette Makro finden Sie in Bild 4.

Makro, das durch das Ereignis Nach Aktualisierung ausgelöst wird

Bild 4: Makro, das durch das Ereignis Nach Aktualisierung ausgelöst wird

Makro beim Löschen

Das Makro, das beim Löschen eines Datensatzes der Tabelle tblKunden ausgelöst werden soll, sieht ähnlich aus. Der einzige Unterschied ist, dass es nicht das Feld GeaendertAm, sondern GeloeschtAm mit dem aktuellen Datum füllt (s. Bild 5). Und natürlich legen Sie es über einen anderen Ribbon-Eintrag an, nämlich Tabelle|Nachfolgeereignis­se|­Nach Löschen. Den Zugriff auf die Feldinhalte des gelöschten Datensatzes liefert wiederum die Tabelle Alt.

Makro, das durch das Ereignis Nach Löschung ausgelöst wird

Bild 5: Makro, das durch das Ereignis Nach Löschung ausgelöst wird

Probieren geht über studieren

Nun müssen wir die beiden Makros noch ausprobieren. Legen Sie also einen neuen Datensatz in der Tabelle an. Nach dem Ändern des Feldes Vorname und dem Speichern des Datensatzes in der Tabelle tblKunden finden Sie in der Tabelle tblKundenArchiv einen neuen Datensatz vor, der den Datensatz in der Form vor der Änderung enthält – samt Änderungsdatum.

Wenn Sie den Datensatz auch noch aus der Tabelle tblKunden löschen, verschwindet dieser zwar aus dieser Tabelle, aber eine Kopie dieses Datensatzes landet in der Tabelle tblKundenArchiv – samt aktuellem Datum im Feld GeloeschtAm.

Die Tabelle tblKundenArchiv sieht danach beispielsweise wie in Bild 6 aus.

Die Tabelle tblKundenArchiv mit einem geänderten und einem gelöschten Datensatz

Bild 6: Die Tabelle tblKundenArchiv mit einem geänderten und einem gelöschten Datensatz

Datenarchiv im Formular

Nun hilft diese Archivierung nicht viel, wenn man es nicht entsprechend einsehen und nutzen kann – zum Beispiel, um einen bestimmten Stand wiederherzustellen. Dazu benötigen wir ein Formular, das sowohl den aktuellen Stand des Datensatzes liefert als auch die archivierten Versionen des Datensatzes.

Am übersichtlichsten ist vermutlich eine Darstellung der Datensätze in der Datenblattansicht. Bevor wir uns an die Umsetzung begeben, wollen wir uns noch überlegen, welche Möglichkeiten uns diese liefern soll.

Sollen Datensätze direkt in der Datenblattansicht geändert werden können? Nein. Es soll nur möglich sein, eine beliebige archivierte Version eines Datensatzes wiederherzustellen – egal, ob der Datensatz wegen einer Änderung oder wegen einer Löschung in der Tabelle tblKundenArchiv gespeichert wurde.

Wir könnten nun zwei Datenblätter übereinander darstellen, indem wir diese in zwei Unterformularen unterbringen. Das Problem dabei ist wie immer, dass die Datenblätter in den beiden Unterformularen möglichst synchron gehandhabt werden sollten – wenn der Benutzer das obere Datenblatt scrollt, sollte dies auch mit dem unteren geschehen, und wenn die Spaltenbreiten in einer von beiden Ansichten geändert werden, sollte dies auch für das jeweils andere Datenblatt durchgeführt werden.

Datenherkunft des Unterformulars

Da wir aber soeben festgelegt haben, dass wir ohnehin nicht direkt Änderungen am aktuellen Datensatz vornehmen wollen und auch nicht an den archivierten Datensätzen, können wir auch den aktuellen Datensatz und die archivierten Datensätze gemeinsam in einem Unterformular in der Datenblattansicht unterbringen.

Dies gelingt ganz einfach durch Zusammenführen des aktuellen Datensatzes aus der Tabelle tblKunden und der archivierten Datensätze aus der Tabelle tblKundenArchiv per UNION-Abfrage.

Dies ist auch dann möglich, wenn der Datensatz gar nicht mehr in der Tabelle tblKunden enthalten ist – der erste Teil der UNION-Abfrage liefert dann halt keine Daten.

Schauen wir uns zunächst den Teil der UNION-Abfrage an, der den Teil der Datensätze beisteuert, die aus der Tabelle tblKundenArchiv stammen.

Diese sollen wie in Bild 7 daherkommen, wo eine Abfrage namens qryKundenArchiv neben den eigentlichen Feldern der Tabelle tblKundenArchiv noch zwei berechnete Felder liefert.

Die Abfrage qryKundenArchiv liefert die Daten der Tabelle tblKundenArchiv und bereitet diese auf.

Bild 7: Die Abfrage qryKundenArchiv liefert die Daten der Tabelle tblKundenArchiv und bereitet diese auf.

Das erste heißt Aktion und soll den Wert Gelöscht am: enthalten, wenn das Feld GeloeschtAm des Datensatzes der Tabelle tblKundenArchiv einen Wert enthält (s. Bild 8). Wenn hingegen das Feld GeaendertAm gefüllt ist, soll das Feld Aktion den Wert Geändert am: anzeigen. Der Ausdruck für dieses Feld sieht so aus:

Die Abfrage qryKundenArchiv in der Entwurfsansicht

Bild 8: Die Abfrage qryKundenArchiv in der Entwurfsansicht

Aktion: Wenn(IstNull([GeaendertAm]);"Gelöscht";"Geändert")

Das zweite berechnete Feld heißt Archivdatum und soll entweder den Inhalt des Feldes GeaendertAm oder GeloeschtAm aufnehmen – je nachdem, welches der beiden einen Wert enthält. Dieses Feld enthält den folgenden Ausdruck:

Archivdatum: Wenn(IstNull([GeaendertAm]);[GeloeschtAm];[GeaendertAm])

Außerdem soll die Abfrage absteigend nach dem Wert dieses Feldes sortieren. Nun wollen wir diese Abfrage mit dem betroffenen Datensatz der Tabelle tblKunden zusammenführen. Dazu legen Sie eine UNION-Abfrage wie die aus Bild 9 an.

UNION-Abfrage

Bild 9: UNION-Abfrage

Damit die Spalte Aktion wie in Bild 10 den Wert Aktuelle Version für den aktuellen Datensatz anzeigt, definieren wir das zweite Feld der UNION-Abfrage mit 'Aktuelle Version' AS Aktion.

Die Abfrage führt den aktuellen Datensatz und seine archivierten Versionen zusammen.

Bild 10: Die Abfrage führt den aktuellen Datensatz und seine archivierten Versionen zusammen.

Das Feld Archivdatum des aktuellen Datensatzes soll, damit dieser immer ganz oben angezeigt wird, mit dem aktuellen Datum und der aktuellen Uhrzeit gefüllt werden. Dies erreichen wir mit dem Ausdruck Now AS Archivdatum.

Bevor wir diese Abfrage nun als Datenherkunft des Unterformulars verwenden, müssen wir diesem noch einen Parameter hinzufügen, der als Vergleichswert eines WHERE-Kriteriums dient und nur diejenigen Datensätze zurückliefert, die dem zu untersuchenden Datensatz entsprechen.

Der Parameter soll prmKundeID heißen und wird in jede SELECT-Anweisung der Abfrage je einmal eingefügt:

SELECT 0 AS ArchivKundeID, 
'Aktuelle Version' AS Aktion, 
Now AS Archivdatum, KundeID, 
AnredeID, Vorname, Nachname, Firma, Strasse, PLZ, Ort, Land
FROM tblKunden 
WHERE KundeID = [prmKundeID];
UNION SELECT ArchivKundeID, Aktion, 
Archivdatum, KundeID, AnredeID, 
Vorname, Nachname, Firma, Strasse, 
PLZ, Ort, Land 
FROM qryKundenArchiv 
WHERE KundeID = [prmKundeID]
ORDER BY Archivdatum;

Unterformular zur Anzeige der Datensätze

Nun erstellen wir zunächst das Unterformular sfmArchivierteDatensaetze. Diesem weisen wir zunächst die Abfrage qryKundenArchiv als Datenherkunft zu, da dieses alle Felder enthält, die später im Unterformular angezeigt werden sollen. Später, wenn der Benutzer das Formular öffnet, soll dieses mit dem Wert des Feldes KundeID des zu untersuchenden Datensatzes der Tabelle tblKunden als Parameter geöffnet werden, woraufhin das Unterformular mit einem Recordset auf Basis der Abfrage qryKundenArchivUnion gefüllt wird, das zuvor bereits mit dem entsprechenden Parameter versehen wurde. Das Unterformular sieht nach dem Hinzufügen der entsprechenden Felder wie in Bild 11 aus.

Das Unterformular sfmArchivierteDatensaetze, hier zunächst mit der Abfrage qryKundenArchiv als Datenherkunft

Bild 11: Das Unterformular sfmArchivierteDatensaetze, hier zunächst mit der Abfrage qryKundenArchiv als Datenherkunft

Damit können wir uns nun gleich an die Erstellung des Hauptformulars begeben, das – mit integriertem Unterformular – im Entwurf wie in Bild 12 aussehen soll. Das Unterformular fügen Sie dem Hauptformular hinzu, indem Sie es einfach aus dem Navigationsbereich in den Detailbereich des Hauptformulars ziehen. Anschließend stellen Sie die Eigenschaften Horizontaler Anker und Vertikaler Anker jeweils auf Beide ein, damit sich das Unterformular beim Vergrößern des Hauptformulars anpasst.

Haupt- und Unterformular in der Entwurfsansicht

Bild 12: Haupt- und Unterformular in der Entwurfsansicht

Außerdem fügen Sie im Hauptformular eine Schaltfläche namens cmdWiederherstellen ein, mit welcher der Benutzer den aktuell markierten Datensatz wiederherstellt und somit den bestehenden Datensatz überschreibt beziehungsweise einen gelöschten Datensatz wiederherstellt.

Unterformular filtern

Zuvor müssen wir allerdings noch sicherstellen, dass das Unterformular auch nur die betroffenen Datensätze anzeigt. Deshalb erstellen wir noch eine ähnliche Kombination aus Haupt- und Unterformular, die im Unterformular alle Kunden aus der Tabelle tblKunden anzeigt und mit cmdHistorie eine Schaltfläche enthält, mit der Sie die Historie für den aktuell im Unterformular ausgewählten Kunden im Formular frmArchivierteDatensaetze anzeigen können (s. Bild 13).

Anzeige aller Kunden und Aufrufen der Historie eines Kunden

Bild 13: Anzeige aller Kunden und Aufrufen der Historie eines Kunden

Die Schaltfläche cmdHistorie löst die Prozedur aus Listing 1 aus, die das Formular frmArchivierteDatensaetze öffnet und mit dem Öffnungsargument den Wert des Feldes KundeID des aktuell ausgewählten Datensatzes übergibt. Diese ist dann im Klassenmodul des Formulars frmArchivierteDatensaetze über die Eigenschaft Me.OpenArgs verfügbar und kann als Parameter der dem Unterformulars dieses Formulars zugrunde liegenden Abfrage verwendet werden.

Private Sub cmdHistorie_Click()
     DoCmd.OpenForm "frmArchivierteDatensaetze", OpenArgs:=Me!sfmArchivierteDatensaetze.Form!kundeID
End Sub

Listing 1: Diese Prozedur öffnet das Formular sfmArchivierteDatensaetze

Kunde mit Archiv anzeigen

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.