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.
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).
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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).
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
Nun müssen wir das Hauptformular frmArchivierteDatensaetze noch mit einer Ereignisprozedur ausstatten, die beim Laden das öffnungsargument auswertet und die Datensätze zum richtigen Kunden im Unterformular anzeigt.
Diese sieht wie in Listing 2 aus. Die Prozedur prüft, ob Me.OpenArgs überhaupt einen Wert enthält. Falls ja, füllt die Prozedur die Variable db mit einem Verweis auf das aktuelle Database-Objekt. Die Variable qdf referenziert dann die Abfrage qryKundenArchivUNION, der mit der folgenden Anweisung der Parameter prmKundeID zugewiesen wird.
Private Sub Form_Load() Dim db As DAO.Database Dim qdf As DAO.QueryDef Dim rst As DAO.Recordset Dim prm As DAO.Parameter If Not IsNull(Me.OpenArgs) Then Set db = CurrentDb Set qdf = db.QueryDefs("qryKundenArchivUNION") Set prm = qdf.Parameters("prmKundeID") prm.Value = Me.OpenArgs Set rst = qdf.OpenRecordset Set Me!sfmArchivierteDatensaetze.Form.Recordset = rst End If End Sub
Listing 2: Ermitteln und Zuweisen der Datenherkunft des Unterformulars beim Laden
Nachdem dieser den Wert erhalten hat, den Me.OpenArgs liefert, kann die Prozedur ein Recordset-Objekt auf Basis dieser Abfrage mit dem gegebenen Parameter erstellen und dieses dem Unterformular als Datenherkunft zuweisen.
Die festgelegte Sortierung sorgt dafür, dass der aktuelle Datensatz ganz oben angezeigt wird, die übrigen folgen dann in der Reihenfolge der änderung weiter unten.
Unterschiede anzeigen
Nun wäre es hilfreich, die Unterschiede zwischen den Datensätzen anzuzeigen. Wie aber sollen wir hier vorgehen Man könnte alle Unterschiede der archivierten Datensätze zum aktuellen Datensatz anzeigen. Alternativ zeigt man alle Unterschiede an, die sich von Version zu Version ergeben haben. Da dies ein schönes Beispiel für den Einsatz der bedingten Formatierung ist, wenden wir einfach beide Versionen an – der Benutzer kann dann zwischen den Versionen umschalten.
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo