{"id":55000672,"date":"2009-08-01T00:00:00","date_gmt":"2020-05-22T22:16:14","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=672"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"Aenderungsdaten_protokollieren","status":"publish","type":"post","link":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/","title":{"rendered":"&Auml;nderungsdaten protokollieren"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>Wenn mehrere Benutzer mit den Daten einer Datenbank arbeiten, m&ouml;chten Sie f&uuml;r die relevanten Tabellen vielleicht nachhalten, wer wann welchen Datensatz angelegt, bearbeitet oder gel&ouml;scht hat. Dazu sind zwei Schritte n&ouml;tig: Das Hinzuf&uuml;gen der Felder zum Speichern dieser Informationen in den relevanten Tabellen und das Eintragen dieser Informationen, wenn es soweit ist. Dieser Beitrag zeigt, wie Sie mit minimalem Aufwand ein solches Protokoll einrichten. <\/b><\/p>\n<p>Eine detaillierte Historie der &auml;nderungen an Daten samt Archivierung der Daten vor jeder &auml;nderung ist aufwendig und wird nur selten ben&ouml;tigt. H&auml;ufiger trifft man den Fall an, dass man einfach nur wissen m&ouml;chte, wer zum Beispiel einen Kunden angeleg oder wer eine Bestellung zuletzt ge&auml;ndert oder gel&ouml;scht hat.<\/p>\n<p>Beim Anlegen und Bearbeiten der Daten ist dies gar kein Problem: Sie f&uuml;gen der Tabelle einfach je zwei Felder zum Speichern des Datums der Aktion und des Mitarbeiters hinzu und sehen an den entsprechenden Stellen, also beispielsweise in den Formularen zur Bearbeitung dieser Daten, die ben&ouml;tigten Codezeilen zum Eintragen der &auml;nderungsdaten vor.<\/p>\n<p>Beim L&ouml;schen wird dies schon schwieriger: Wo wollen Sie ein L&ouml;schdatum und einen Verweis auf den Verantwortlichen unterbringen, wenn der Datensatz anschlie&szlig;end nicht mehr vorhanden ist Ganz einfach: Sie l&ouml;schen den Datensatz gar nicht, sondern versehen ihn mit einem Marker.<\/p>\n<p>So k&ouml;nnen Sie auch das L&ouml;schdatum und den l&ouml;schenden Mitarbeiter ganz einfach hinzuf&uuml;gen. Dass der Datensatz ja nun gar nicht physisch gel&ouml;scht ist, ist zwar kein Problem, bringt aber ein wenig zus&auml;tzlichen Aufwand mit sich: Alle Abfragen, Formulare, Berichte, Steuerelemente und Codeabschnitte, die sich auf die Daten der Tabelle mit den vermeintlich gel&ouml;schten Datens&auml;tzen beziehen, m&uuml;ssen um eine entsprechende <b>WHERE<\/b>-Klausel erg&auml;nzt werden, die alle Datens&auml;tze ausschlie&szlig;t, f&uuml;r die der Wert eines Feldes etwa mit dem Namen <b>GeloeschtAm <\/b>nicht Null ist.<\/p>\n<p>Im Gegenzug k&ouml;nnen Sie aber auch sp&auml;ter noch auf die Daten zugreifen, die bereits gel&ouml;scht wurden &#8211; beispielsweise, um diese entg&uuml;ltig zu l&ouml;schen, zu archivieren oder um Auswertungen auf den gesamten Datenbestand zu fahren.<\/p>\n<p><b>Tabellen anpassen<\/b><\/p>\n<p>Wie &uuml;blich m&ouml;chten wir den Aufwand zum Einbau dieser L&ouml;sung m&ouml;glichst gering halten. Das betrifft zum Beispiel das Hinzuf&uuml;gen der Felder mit den &auml;nderungsdaten zu den betroffenen Tabellen.<\/p>\n<p>Normalerweise &ouml;ffnet man jede dieser Tabellen im Entwurf, f&uuml;gt die gew&uuml;nschten Felder hinzu, verkn&uuml;pft diese gegebenenfalls mit einer Mitarbeitertabelle, um sich per Fremdschl&uuml;ssel auf die ausf&uuml;hrenden Mitarbeiter beziehen zu k&ouml;nnen, und speichert dann die Tabelle.<\/p>\n<p>Und das erledigt man nicht nur f&uuml;r eine, sondern f&uuml;r alle Tabellen, deren &auml;nderungsdaten protokolliert werden sollen.<\/p>\n<p>Puh, das ist aber langweilig &#8211; da setzen wir uns doch lieber mal ein paar Minuten hin und programmieren eine kleine Funktion, die uns diese Aufgabe abnimmt.<\/p>\n<p>Voraussetzung f&uuml;r die nachfolgend beschriebene Prozedur ist eine Benutzertabelle und die Anmeldung der Benutzer beim Start der Datenbank. F&uuml;r Beispielzwecke soll diese m&ouml;glichst einfach aussehen &#8211; zum Beispiel wie in Bild 1.<\/p>\n<p><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2009_04\/Aenderungshistorie-web-images\/pic001_opt.jpeg\" alt=\"pic001.png\" \/><\/p>\n<p><b><span style=\"color:darkgrey\">Bild 1: Diese Tabelle soll in den Feldern zum Eintragen der &auml;nderungen per Fremdschl&uuml;ssel referenziert werden.<\/span><\/b><\/p>\n<p>Die &auml;nderungsfelder legen wir mit einigen SQL-Anweisungen an, die wir mit der <b>Execute<\/b>-Methode des <b>Database<\/b>-Objekts ausf&uuml;hren. Werfen wir zun&auml;chst einen Blick auf die nackten SQL-Anweisungen. Die erste soll einfach nur ein Datumsfeld namens <b>AngelegtAm <\/b>hinzuf&uuml;gen und sieht so aus:<\/p>\n<pre>ALTER TABLE tblKunden\r\nADD AngelegtAm DATETIME<\/pre>\n<p>Sie k&ouml;nnen diese Anweisung ausprobieren, indem Sie eine neue, leere Abfrage im Entwurf &ouml;ffnen und dann zur SQL-Ansicht wechseln. Dort tragen Sie den SQL-Ausdruck ein und f&uuml;hren ihn aus.<\/p>\n<p>Wir brauchen noch zwei weitere Anweisungen, von denen die erste das Feld zum Speichern des durchf&uuml;hrenden Benutzers anlegt und das zweite die Beziehung von diesem Fremdschl&uuml;sselfeld zur Tabelle <b>tblBenutzer<\/b>:<\/p>\n<pre>ALTER TABLE tblKunden ADD AngelegtDurch INTEGER\r\nALTER TABLE tblKunden ADD CONSTRAINT\r\nFKAngelegtDurch FOREIGN KEY (AngelegtDurch)\r\nREFERENCES tblKunden<\/pre>\n<p>Zusammengefasst in einer Funktion sehen die SQL-Anweisungen wie in Listing 1 aus. Der Aufruf f&uuml;r unsere Beispielkonstellation mit einer Tabelle <b>tblBenutzer<\/b> f&uuml;r die Benutzerdaten und mit einer Tabelle <b>tblKunden<\/b>, die die Felder zum Eintragen der &auml;nderungsdaten erhalten soll, sieht so aus:<\/p>\n<p class=\"kastentabelleheader\">Listing 1: Hinzuf&uuml;gen von History-Feldern zu einer Tabelle<\/p>\n<pre>Public Function AddHistoryFieldsToTable(strTableChange As String, strTableUsers As String)\r\n    ''AngelegtAm und AngelegtDurch\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD AngelegtAm DATETIME&quot;, dbFailOnError\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD AngelegtDurch INTEGER&quot;, dbFailOnError\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD CONSTRAINT FKAngelegtDurch &quot; _\r\n    &amp; &quot;FOREIGN KEY (AngelegtDurch) REFERENCES &quot; &amp; strTableUsers, dbFailOnError\r\n    ''ZuletztGeaendertAm und ZuletztGeaendertDurch\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD ZuletztGeaendertAm DATETIME&quot;, dbFailOnError\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD ZuletztGeaendertDurch INTEGER&quot;, dbFailOnError\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD CONSTRAINT FKGeaendertDurch &quot; _\r\n    &amp; &quot;FOREIGN KEY (GeaendertDurch) REFERENCES &quot; &amp; strTableUsers, dbFailOnError\r\n    ''GeloeschtAm und GeloeschtDurch\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD GeloeschtAm DATETIME&quot;, dbFailOnError\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD GeloeschtDurch INTEGER&quot;, dbFailOnError\r\n    dbs.Execute &quot;ALTER TABLE &quot; &amp; strTableChange &amp; &quot; ADD CONSTRAINT FKGeloeschtDurch &quot; _\r\n    &amp; &quot;FOREIGN KEY (GeloeschtDurch) REFERENCES &quot; &amp; strTableUsers, dbFailOnError\r\nEnd Function\r\nAddHistoryFieldsToTable &quot;tblKunden&quot;, &quot;tblBenutzer&quot;<\/pre>\n<p>Verwenden Sie diesen Aufruf f&uuml;r alle Tabellen, f&uuml;r deren Datens&auml;tze Sie &auml;nderungsinformationen speichern m&ouml;chten. Das Ergebnis sieht wie in Bild 2 aus. Was Sie hier nicht erkennen k&ouml;nnen, sind die Beziehungen, welche die Funktion aus Listing 1 zwischen den Fremdschl&uuml;sselfeldern <b>AngelegtDurch<\/b>, <b>ZuletztGeaendertAm<\/b> und <b>GeloeschtAm <\/b>und der Tabelle <b>tblBenutzer <\/b>anlegt. Abb. 3 liefert den Beweis, dass auch die Beziehungen ordnungsgem&auml;&szlig; angelegt wurden.<\/p>\n<p><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2009_04\/Aenderungshistorie-web-images\/pic002_opt.jpeg\" alt=\"pic002.png\" \/><\/p>\n<p><b><span style=\"color:darkgrey\">Bild 2: Eine Beispieltabelle mit Feldern zum Speichern von &auml;nderungsinformationen<\/span><\/b><\/p>\n<p><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2009_04\/Aenderungshistorie-web-images\/pic003_opt.jpeg\" alt=\"pic003.png\" \/><\/p>\n<p><b><span style=\"color:darkgrey\">Bild 3: Diese Beziehungen wurden mit der Funktion aus Listing 1 angelegt.<\/span><\/b><\/p>\n<p><b>Alternative Tabellenanpassung<\/b><\/p>\n<p>Es gibt noch eine Alternative zur beschriebenen Vorgehensweise: Dabei w&uuml;rden Sie die sechs Felder zum Festhalten der &auml;nderungsinformationen nicht direkt in die Tabellen schreiben, sondern in eine separate Tabelle. Diese enth&auml;lt neben den sechs Feldern noch zwei weitere Felder zum Speichern der Tabelle und des Datensatzes, auf die sich die Informationen beziehen.<\/p>\n<p>Der Nachteil dieser Vorgehensweise ist, dass der Aufwand f&uuml;r das Herausfiltern von als gel&ouml;scht markierten Daten relativ hoch ist. Daher sehen wir an dieser Stelle von einer ausf&uuml;hrlicheren Beschreibung dieser Variante ab.<\/p>\n<p><b>&auml;nderungsdaten pflegen<\/b><\/p>\n<p>Der n&auml;chste Schritt ist etwas aufwendiger: Sie m&uuml;ssen pr&uuml;fen, an welchen Stellen der Datenbank Operationen auf Basis der Daten dieser Tabelle durchgef&uuml;hrt werden. &Uuml;berall dort m&uuml;ssen Sie per Code oder &uuml;ber entsprechende &auml;nderungen an eventuell vorhandenen Aktionsabfragen daf&uuml;r sorgen, dass bei &auml;nderungen auch die n&ouml;tigen Informationen in die soeben angelegten Felder eingetragen werden.<\/p>\n<p>Leichter haben Sie es, wenn Sie die Anwendung ohnehin gerade erst erstellen &#8211; Sie laufen dann weniger Gefahr, die Stellen, an denen die Daten ge&auml;ndert werden, zu &uuml;bersehen.<\/p>\n<p>Vielleicht ist das Ganze aber auch gar nicht so aufwendig. Gehen wir einmal von der Annahme aus, dass Sie Daten nur &uuml;ber Formulare, &uuml;ber die <b>Execute<\/b>-Methode des <b>Database<\/b>-Objekts oder per DAO mit <b>AddNew<\/b>, <b>Edit <\/b>und <b>Update <\/b>&auml;ndern. Dann ergeben sich m&ouml;glicherweise verallgemeinernde Vereinfachungen &#8211; aber schauen Sie doch selbst.<\/p>\n<p><b>&auml;nderungsdaten in Formularen<\/b><\/p>\n<p>In Formularen gibt es zwei Ereignisse, die beim Speichern eines neuen oder ge&auml;nderten beziehungsweise beim L&ouml;schen eines Datensatzes ausgel&ouml;st werden. Das Ereignis <b>Vor Aktualisierung <\/b>beispielsweise feuert immer beim Speichern eines ge&auml;nderten Datensatzes, egal, ob es sich um einen neuen oder einen vorhandenen Datensatz handelt.<\/p>\n<p>Zu Beispielzwecken legen Sie nun ein einfaches Formular an, das alle Felder der oben beschriebenen Tabelle <b>tblKunden <\/b>enth&auml;lt. Erstellen Sie dann eine Prozedur f&uuml;r die Ereigniseigenschaft <b>Vor Aktualisierung<\/b>, die wie folgt aussieht:<\/p>\n<pre>Private Sub Form_BeforeUpdate(Cancel As Integer)\r\n    If Me.NewRecord = True Then\r\n        Me!AngelegtAm = Now\r\n    Else\r\n        Me!ZuletztGeaendertAm = Now\r\n    End If\r\n    End Sub<\/pre>\n<p>Die Prozedur pr&uuml;ft, ob gerade ein neu angelegter (<b>Me.NewRecord = True<\/b>) oder ein bestehender Datensatz ge&auml;ndert wurde und nun gespeichert werden soll. Im Falle eines neuen Datensatzes schreibt die Routine noch das aktuelle Datum samt Zeit in das Feld <b>AngelegtAm<\/b>, bei einem vorhandenen Datensatz ist das Feld <b>ZuletztGeaendertAm <\/b>das Ziel. Das ist alles &#8211; &auml;nderungen im Formular werden so zuverl&auml;ssig protokolliert.<\/p>\n<p>Betrachten wir nun das L&ouml;schen eines Datensatzes. Das L&ouml;schen l&ouml;st das Ereignis <b>Beim L&ouml;schen <\/b>aus, wof&uuml;r Sie die folgende Routine hinterlegen:<\/p>\n<pre>Private Sub Form_Delete(Cancel As Integer)\r\n    Me!GeloeschtAm = Now\r\n    Me.Dirty = False\r\n    Cancel = True\r\n    End Sub<\/pre>\n<p><!--30percent--><\/p>\n<p>Die Routine stellt das L&ouml;schdatum des Datensatzes auf das aktuelle Datum samt Zeit ein, speichert den Datensatz (<b>Me.Dirty = True<\/b>) und bricht den L&ouml;schvorgang ab, damit der Datensatz nicht wirklich gel&ouml;scht wird (sonst h&auml;tten wir wohl nichts vom frisch ge&auml;nderten L&ouml;schdatum &#8230;).<\/p>\n<p>Wenn Sie dies nun im Formular ausprobieren, werden Sie feststellen, dass diese Routine dummerweise auch das letzte &auml;nderungsdatum beeinflusst, denn das Eintragen des L&ouml;schdatums und das anschlie&szlig;ende Speichern ist nat&uuml;rlich auch eine &auml;nderung, die vom <b>Vor Aktualisierung<\/b>-Ereignis registriert wird.<\/p>\n<p>Wir m&uuml;ssen der Routine <b>Form_BeforeUpdate<\/b> also irgendwie mitteilen, dass sie das &auml;nderungsdatum beim L&ouml;schen eines Datensatzes und der damit verbundenen &auml;nderung des Datensatzes nicht ber&uuml;cksichtigt. Dazu f&uuml;hren wir die folgende formularmodulweit g&uuml;ltige Variable ein:<\/p>\n<pre>Dim bolDelete As Boolean<\/pre>\n<p>Sie soll vor dem Speichern des Datensatzes w&auml;hrend des L&ouml;schvorgangs auf den Wert <b>True <\/b>und anschlie&szlig;end wieder auf <b>False <\/b>eingestellt werden:<\/p>\n<pre>Private Sub Form_Delete(Cancel As Integer)\r\n    Me!GeloeschtAm = Now\r\n    bolDelete = True\r\n    Me.Dirty = False\r\n    bolDelete = False\r\n    Cancel = True\r\n    End Sub<\/pre>\n<p>Damit die <b>Vor Aktualisierung<\/b>-Ereignisprozedur dies entsprechend verarbeitet, soll sie vor dem &auml;ndern des &auml;nderungsdatums pr&uuml;fen, ob nicht vielleicht gerade ein L&ouml;schvorgang erfolgt und somit das &auml;nderungsdatum beibehalten werden soll:<\/p>\n<pre>Private Sub Form_BeforeUpdate(Cancel As Integer)\r\n    If Me.NewRecord = True Then\r\n        Me!AngelegtAm = Now\r\n    Else\r\n        If bolDelete = False Then\r\n            Me!ZuletztGeaendertAm = Now\r\n        End If\r\n    End If\r\n    End Sub<\/pre>\n<p>Probieren Sie dies nun aus &#8211; es funktioniert. Aber auch nur fast: Der gel&ouml;schte Datensatz wird zwar durch den entsprechenden Eintrag im Feld <b>GeloeschtAm <\/b>als gel&ouml;scht markiert, verschwindet aber nicht aus der Datenblattansicht.<\/p>\n<p>Das liegt an zwei Dingen: Erstens haben wir die Datenherkunft noch gar nicht entsprechend eingestellt, das hei&szlig;t, dass einfach alle Datens&auml;tze der Tabelle <b>tblKunden <\/b>angezeigt werden.<\/p>\n<p>Zweitens w&uuml;rde es selbst dann nicht funktionieren, weil die Datenherkunft w&auml;hrend des L&ouml;schens eines Datensatzes nicht aktualisiert werden kann.<\/p>\n<p>Wir m&uuml;ssen also noch zwei &auml;nderungen vornehmen. Die erste bezieht sich auf die Datenherkunft, die von nun an aus der folgenden Abfrage bestehen soll:<\/p>\n<pre>SELECT * FROM tblKunden WHERE GeloeschtAm IS NULL<\/pre>\n<p>Dies f&uuml;hrt dazu, dass nur noch solche Datens&auml;tze angezeigt werden, deren Feld <b>GeloeschtAm <\/b>noch leer ist und die dementsprechend noch nicht gel&ouml;scht wurden. &Uuml;brigens sollten Sie die Felder mit den &auml;nderungsdaten dringend mit einem Index versehen, da sonst schnell die Performance in den Keller geht.<\/p>\n<p>Das zweite Problem ist schwieriger zu l&ouml;sen. Wie bringen wir dem Formular bei, seine Datenherkunft unabh&auml;ngig vom L&ouml;schvorgang zu aktualisieren Hier hilft nur das gute, alte <b>Timer<\/b>-Ereignis.<\/p>\n<p>Dieses wird ja normalerweise in regelm&auml;&szlig;igen zeitlichen Abst&auml;nden, die &uuml;ber die Eigenschaft <b>Zeitgeberintervall <\/b>festgelegt werden, ausgel&ouml;st. Hier soll dieses Ereignis nicht von Beginn an feuern, daher behalten Sie den Wert <b>0 <\/b>f&uuml;r die Eigenschaft <b>Zeitgeberintervall <\/b>bei.<\/p>\n<p>Die Eigenschaft soll erst ge&auml;ndert werden, wenn der L&ouml;schvorgang l&auml;uft. Daher stellen Sie diese innerhalb der Ereignisprozedur, die durch das Ereignis <b>Nach Aktualisierung <\/b>ausgel&ouml;st wird, auf den Wert <b>100 <\/b>ein (entspricht einer zehntel Sekunde):<\/p>\n<pre>Private Sub Form_AfterUpdate()\r\n    Me.TimerInterval = 100\r\n    End Sub<\/pre>\n<p>Aus der oben bereits beschriebenen Ereignisprozedur <b>Beim L&ouml;schen <\/b>entfernen Sie die Zeile <b>bolDelete = False<\/b>:<\/p>\n<pre>Private Sub Form_Delete(Cancel As Integer)\r\n    Me!GeloeschtAm = Now\r\n    bolDelete = True\r\n    Me.Dirty = False\r\n    Cancel = True\r\n    End Sub<\/pre>\n<p>Schlie&szlig;lich definieren Sie noch eine Prozedur, die durch den Zeitgeber ausgel&ouml;st wird. Diese sieht wie folgt aus:<\/p>\n<pre>Private Sub Form_Timer()\r\n    Me.TimerInterval = 0\r\n    If bolDelete Then\r\n        Me.Requery\r\n        Me.Recordset.MoveLast\r\n    End If\r\n    bolDelete = False\r\n    End Sub<\/pre>\n<p>Die Routine stellt zun&auml;chst den Wert von <b>TimerInterval <\/b>auf <b>0 <\/b>ein, damit die Routine nur einmal pro L&ouml;schvorgang ausgel&ouml;st wird. Dann pr&uuml;ft sie den Wert der Variablen <b>bolDeleted<\/b>, der in der Routine <b>Form_Delete <\/b>auf <b>True <\/b>eingestellt wurde und die Durchf&uuml;hrung eines L&ouml;schvorgangs anzeigt.<\/p>\n<p>Hat diese den Wert <b>True<\/b>, wird das Formular aktualisiert und schlie&szlig;lich die Variable <b>bolDelete <\/b>auf <b>False <\/b>eingestellt, damit die n&auml;chste &auml;nderung nicht ebenfalls als L&ouml;schung interpretiert wird.<\/p>\n<p><b>Protokollieren von &auml;nderungen in SQL-Anweisungen<\/b><\/p>\n<p>Nicht alle &auml;nderungen erfolgen zwangsl&auml;ufig &uuml;ber Formulare, sondern auch &uuml;ber die (nicht so praktische) Methode <b>DoCmd.RunSQL <\/b>und &uuml;ber die zu bevorzugende <b>Execute<\/b>-Methode des <b>Database<\/b>-Objekts. Diese Methoden dienen unter anderem dazu, Aktionsabfragen wie die Anf&uuml;geabfrage (<b>INSERT INTO<\/b>), die Aktualisierungsabfrage (<b>UPDATE<\/b>) und die L&ouml;schabfrage (<b>DELETE<\/b>) durchzuf&uuml;hren.<\/p>\n<p>Normalerweise k&ouml;nnen Sie sich hier nur einklinken, wenn Sie ein aktives Datenbankmanagementsystem wie MS SQL Server oder MySQL verwenden und die dort verf&uuml;gbaren Trigger einsetzen (mehr zu diesem Thema im Beitrag <b>Trigger<\/b>, Shortlink 636).<\/p>\n<p>Wir bauen eine L&ouml;sung, die geringf&uuml;gige &auml;nderungen am bestehenden Code erfordert, aber sonst sehr komfortabel ist.<\/p>\n<p>Dazu definieren wir eine neue Klasse namens <b>clsDB<\/b>. Diese enth&auml;lt nur eine einzige Prozedur namens <b>Execute<\/b>. In einem Standardmodul deklarieren Sie ein Objekt namens <b>dba <\/b>wie folgt:<\/p>\n<pre>Public dba As New clsDB<\/pre>\n<p>Das auf der Klasse <b>clsDB <\/b>basierende Objekt ist wegen des <b>New<\/b>-Schl&uuml;sselworts nun st&auml;ndig verf&uuml;gbar.<\/p>\n<p>Die Klasse enth&auml;lt zun&auml;chst nur eine einzige Methode namens <b>Execute <\/b>(s. Listing 2). Diese hat die gleichen Parameter wie die herk&ouml;mmliche <b>Execute<\/b>-Methode des Database-Objekts und arbeitet auch so &auml;hnlich.<\/p>\n<p class=\"kastentabelleheader\">Listing 2: Execute-Ersatz f&uuml;r das Durchf&uuml;hren von Aktionsabfragen inklusive Schreiben von &auml;nderungsinformationen<\/p>\n<pre>Public Sub Execute(strQuery As String, Optional lngOptions As Long)\r\n...\r\nintPosStart = InStr(1, strQuery, &quot; &quot;)\r\nstrAction = Trim(Left(strQuery, intPosStart))\r\nSelect Case strAction\r\nCase &quot;INSERT&quot;\r\nintPosStart = Len(&quot;INSERT INTO &quot;)\r\nintPosEnde = InStr(intPosStart, strQuery, &quot;(&quot;)\r\nintPosEndeTemp = InStr(intPosStart, strQuery, &quot; SELECT &quot;)\r\nIf intPosEnde &lt; intPosEndeTemp And intPosEnde = 0 Then intPosEnde = intPosEndeTemp\r\nstrAction = &quot;INSERT INTO&quot;\r\nstrTable = Trim(Mid(strQuery, intPosStart, intPosEnde - intPosStart))\r\ndbs.Execute strQuery, lngOptions\r\nstrSQLHistory = &quot;UPDATE &quot; &amp; strTable &amp; &quot; SET AngelegtAm = &quot; &amp; ISODatum(Now) _\r\n&amp; &quot; WHERE AngelegtAm IS NULL&quot;\r\ndbs.Execute strSQLHistory, dbFailOnError\r\nCase &quot;UPDATE&quot;\r\nintPosStart = Len(&quot;UPDATE &quot;)\r\nintPosEnde = InStr(intPosStart, strQuery, &quot;SET&quot;)\r\nstrAction = &quot;UPDATE&quot;\r\nstrTable = Trim(Mid(strQuery, intPosStart, intPosEnde - intPosStart))\r\nintPosStart = InStr(1, strQuery, &quot; WHERE &quot;)\r\nintPosEnde = Len(strQuery)\r\nstrWHERE = Mid(strQuery, intPosStart, intPosEnde - intPosStart + 1)\r\nstrSQLHistory = &quot;UPDATE &quot; &amp; strTable &amp; &quot; SET ZuletztGeaendertAm = &quot; &amp; ISODatum(Now) _\r\n&amp; strWHERE\r\ndbs.Execute strSQLHistory, dbFailOnError\r\ndbs.Execute strQuery, lngOptions\r\nCase &quot;DELETE&quot;\r\nintPosStart = InStr(1, strQuery, &quot; FROM &quot;) + 6\r\nintPosEnde = InStr(intPosStart, strQuery, &quot; WHERE &quot;)\r\nIf intPosEnde = 0 Then\r\n intPosEnde = Len(strQuery)\r\nEnd If\r\nstrAction = &quot;DELETE&quot;\r\nstrTable = Trim(Mid(strQuery, intPosStart, intPosEnde - intPosStart + 1))\r\nintPosStart = InStr(1, strQuery, &quot; WHERE &quot;)\r\nIf intPosStart &gt; 0 Then\r\n intPosEnde = Len(strQuery)\r\n    strWHERE = Mid(strQuery, intPosStart, intPosEnde - intPosStart + 1)\r\nEnd If\r\nIf Len(strWHERE) = 0 Then\r\n strWHERE = &quot; WHERE &quot;\r\nElse\r\n strWHERE = strWHERE &amp; &quot; AND &quot;\r\nEnd If\r\nstrWHERE = strWHERE &amp; &quot;GeloeschtAm IS NULL&quot;\r\nstrSQLHistory = &quot;UPDATE &quot; &amp; strTable &amp; &quot; SET GeloeschtAm = &quot; &amp; ISODatum(Now) &amp; strWHERE\r\ndbs.Execute strSQLHistory, dbFailOnError\r\nEnd Select\r\nEnd Sub<\/pre>\n<p>Betrachten wir zun&auml;chst, was Sie in bestehendem Code &auml;ndern m&uuml;ssen, damit ein Datensatz etwa beim &auml;ndern automatisch das &auml;nderungsdatum im Feld <b>ZuletztGeaendertAm <\/b>erh&auml;lt. Ein solcher Aufruf w&uuml;rde, wenn Sie den Verweis auf <b>CurrentDB <\/b>in einer Variablen namens <b>db <\/b>speichern, so aussehen:<\/p>\n<pre>db.Execute &quot;UPDATE tblKunden SET Kundenname = ''M&uuml;ller'' WHERE Kundenname = ''Mueller''&quot;<\/pre>\n<p>Wenn Sie m&ouml;chten, dass die <b>Execute<\/b>-Methode aus der Klasse <b>clsDB <\/b>zus&auml;tzlich zur &auml;nderung auch noch das &auml;nderungsdatum eintr&auml;gt, verwenden Sie folgenden Aufruf &#8211; der minimale Unterschied ist fett hervorgehoben:<\/p>\n<pre>db<b>a<\/b>.Execute &quot;UPDATE tblKunden SET Kundenname = ''M&uuml;ller'' WHERE Kundenname = ''Mueller''&quot;<\/pre>\n<p>Sie sehen: Der Aufwand h&auml;lt sich in Grenzen. Genau so sieht es &uuml;brigens auch bei <b>DELETE<\/b>&#8211; und bei <b>INSERT INTO<\/b>-Anweisungen aus.<\/p>\n<p><b>Abfangen von INSERT INTO<\/b><\/p>\n<p>Wie aber funktioniert die <b>Execute<\/b>-Methode nun genau Betrachten wir zun&auml;chst den einfachsten Fall &#8211; die <b>INSERT<\/b>-Methode. Sie ist deshalb so einfach, weil man einen neu hinzugef&uuml;gten Datensatz daran erkennt, dass sein Feld <b>AngelegtAm <\/b>noch keinen Wert enth&auml;lt &#8211; zumindest, wenn man bis dahin immer darauf geachtet hat, dass ein solcher Wert eingetragen wird.<\/p>\n<p>Die <b>Execute<\/b>-Methode ermittelt zun&auml;chst den Typ der Aktionsabfrage, indem sie den Text bis zum ersten Leerzeichen ausliest und damit die Zweige eines <b>Select Case<\/b>-Konstrukts ansteuert.<\/p>\n<p>Der Codeabschnitt, der sich um diesen Abfragetyp k&uuml;mmert, befindet sich im <b>Select Case<\/b>-Zweig <b>Case &quot;INSERT&quot;<\/b>. Dem <b>INSERT INTO<\/b> einer solchen Aktionsabfrage folgt immer der Tabellenname und dann entweder eine <b>SELECT<\/b>-Abfrage mit dem Quellausdruck oder eine &ouml;ffnende Klammer gefolgt von den Zielfeldern:<\/p>\n<pre>INSERT INTO tblKunden SELECT ...\r\nINSERT INTO tblKunden(Kundenname ...<\/pre>\n<p>Die Methode will nicht pr&uuml;fen, ob eine <b>SELECT<\/b>-Abfrage als Datenherkunft dient oder ob gezielt bestimmte Felder mit Daten gef&uuml;llt werden sollen &#8211; sie will einfach nur wissen, in welchem Bereich der SQL-Abfrage sich der Tabellenname befindet.<\/p>\n<p>Also muss sie herausfinden, ob hinter <b>INSERT INTO <\/b>zuerst ein <b>SELECT <\/b>oder eine Klammer folgt, und die Position des ersten Zeichens des Siegers ermitteln. Mit dieser Zahl ermittelt die Routine dann leicht den Namen der Zieltabelle, der f&uuml;r die folgenden Schritte schon v&ouml;llig ausreicht:<\/p>\n<ul>\n<li class=\"aufz-hlung\">Ausf&uuml;hren der SQL-Anweisung &uuml;ber die <b>Execute<\/b>-Methode des &#8222;normalen&quot; <b>Database<\/b>-Objekts, hier durch die Variable <b>dbs <\/b>repr&auml;sentiert, und<\/li>\n<li class=\"aufz-hlung\">Ausf&uuml;hren einer neu zusammengesetzten SQL-Anweisung, die einfach allen Datens&auml;tzen ein neues Anlage-Datum hinzuf&uuml;gt, die noch keines besitzen.<\/li>\n<\/ul>\n<p><b>Abfangen von UPDATE<\/b><\/p>\n<p>Bei der <b>UPDATE<\/b>-Anweisung sieht das schon anders aus: Hier kann man nicht einfach die UPDATE-Anweisung ausf&uuml;hren und dann allen Datens&auml;tzen, die noch keinen Wert im Feld <b>ZuletztGeaendertAm <\/b>aufweisen, die aktuelle Zeit zuweisen.<\/p>\n<p>Hier ist die Vorgehensweise ein wenig diffiziler: Wir brauchen n&auml;mlich nicht nur die Zieltabelle, sondern auch noch die <b>WHERE<\/b>-Bedingung und gegebenenfalls angegebene <b>INNER JOIN<\/b>s und die entsprechenden Tabellen- und Feldnamen.<\/p>\n<p>Der <b>UPDATE<\/b>-Zweig der <b>Select Case<\/b>-Bedingung identifiziert zwei Elemente der mit dem Parameter <b>strQuery <\/b>&uuml;bergebenen Abfrage: den Ausdruck mit der oder den Tabellen und die <b>WHERE<\/b>-Klausel.<\/p>\n<p>Aus der folgenden Anweisung w&uuml;rde die Routine also etwa die Ausdr&uuml;cke <b>tblKunden INNER JOIN tblProjekte ON tblKunden.KundeID = tblProjekte.KundeID <\/b>und <b>WHERE Projekt = &#8220;lala&#8220;&quot; <\/b>extrahieren:<\/p>\n<pre>UPDATE tblKunden INNER JOIN tblProjekte\r\nON tblKunden.KundeID = tblProjekte.KundeID\r\nSET Kundenname = ''Kurt'' WHERE Projekt = ''lala''&quot;<\/pre>\n<p>Daraus baut die Routine eine neue Abfrage zusammen, die f&uuml;r das vorliegende Beispiel so aussieht:<\/p>\n<pre>UPDATE tblKunden INNER JOIN tblProjekte\r\nON tblKunden.KundeID = tblProjekte.KundeID\r\nSET ZuletztGeaendertAm = #2009\/05\/12 20:49:55#\r\nWHERE Projekt = ''lala''<\/pre>\n<p>Die Frage stimmt mit der vorherigen &uuml;berein, aber sie &auml;ndert nicht das dort f&uuml;r die &auml;nderung vorgesehene Feld (dies k&ouml;nnten auch mehrere Felder sein), sondern das Feld <b>ZuletztGeaendertAm<\/b>.<\/p>\n<p>Beim Ausf&uuml;hren der beiden Abfragen spielt die Reihenfolge eine entscheidende Rolle: Zun&auml;chst m&uuml;ssen Sie n&auml;mlich das neue &auml;nderungsdatum eintragen und erst dann die eigentlichen &auml;nderungen durchf&uuml;hren. Der Grund ist einfach: Es k&ouml;nnte sonst passieren, dass die eigentliche Aktionsabfrage einen in der <b>WHERE<\/b>-Klausel verwendeten Wert &auml;ndert, wodurch die Aktionsabfrage zum Eintragen des &auml;nderungsdatums den entsprechenden Datensatz nicht mehr findet.<\/p>\n<p><b>L&ouml;schen ohne zu l&ouml;schen<\/b><\/p>\n<p>Richtig interessant wird es beim L&ouml;schen eines Datensatzes, wobei der L&ouml;schvorgang ja eigentlich gar keiner ist, sondern nur das Setzen eines L&ouml;schen-Flags (hier das L&ouml;schdatum) erledigt.<\/p>\n<p>Nehmen wir eine einfache L&ouml;schabfrage als Beispiel:<\/p>\n<pre>DELETE FROM tblKunden\r\nWHERE Kundenname = ''Minhorst''<\/pre>\n<p>Der Datensatz soll aber gar nicht gel&ouml;scht, sondern nur um einen Eintrag im Feld <b>GeloeschtAm <\/b>erweitert werden. Die neue <b>Execute<\/b>-Methode verwirft die als Parameter &uuml;bergebene <b>DELETE<\/b>-Anweisung komplett und erstellt stattdessen eine <b>UPDATE<\/b>-Anweisung, die f&uuml;r dieses Beispiel so aussieht:<\/p>\n<pre>UPDATE tblKunden SET GeloeschtAm = #2009\/05\/12 20:55:38# WHERE Kundenname = ''Minhorst'' AND GeloeschtAm IS NULL<\/pre>\n<p>&Uuml;bernommen wird hier nur der Name der betroffenen Tabelle und die <b>WHERE<\/b>-Bedingung. Neben dem Setzen des Werts f&uuml;r das Feld <b>GeloeschtAm <\/b>erledigt die Abfrage noch eine wichtige Aufgabe &#8211; sie f&uuml;gt ein weiteres Element an die <b>WHERE<\/b>-Klausel an, das sicherstellt, dass nur noch nicht gel&ouml;schte Datens&auml;tze ber&uuml;cksichtigt werden.<\/p>\n<p><b>DAO: Edit, AddNew, Update und Delete<\/b><\/p>\n<p>Nachdem wir die Daten&auml;nderungen in Formularen und mit der <b>Execute<\/b>-Methode ber&uuml;cksichtigt haben, fehlen eigentlich noch die DAO-Methoden <b>AddNew<\/b>, <b>Edit<\/b>, <b>Update <\/b>und <b>Delete<\/b>. Es ist m&ouml;glich, eine Klasse analog zur Klasse <b>clsDB <\/b>zu erstellen, welche diese Funktionen kapselt. Der Aufwand w&auml;re allerdings ungleich h&ouml;her und w&uuml;rde den Rahmen dieses Beitrags sprengen.<\/p>\n<p>Die F&auml;lle, in denen Sie Daten nicht mit der <b>Execute<\/b>-Anweisung &auml;ndern k&ouml;nnen, sondern die entsprechenden DAO-Methoden bem&uuml;hen m&uuml;ssen, sind allerdings &uuml;berschaubar. Sollten sich doch Vorkommen in Ihrem Code finden, ersetzen Sie diese entweder durch entsprechende <b>Execute<\/b>-Methoden oder f&uuml;gen die &auml;nderungsinformationen innerhalb von <b>AddNew<\/b>\/<b>Edit <\/b>und <b>Update <\/b>hinzu beziehungsweise ersetzen die <b>Delete<\/b>-Anweisung entsprechend (soweit ich wei&szlig;, sind solche Vorkommen aber &auml;u&szlig;erst selten).<\/p>\n<p><b>Vorbereiten der Datenherk&uuml;nfte auf gel&ouml;schte Datens&auml;tze<\/b><\/p>\n<p>Ein sehr wichtiger Schritt bei der hier geschilderten Vorgehensweise ist die Anpassung der Datenherk&uuml;nfte von Formularen, Berichten, Steuerelementen und auch innerhalb von VBA-Routinen f&uuml;r das Herausfiltern von als gel&ouml;scht markierten Datens&auml;tzen.<\/p>\n<p>Diese verschwinden ja nicht mehr physisch aus dem Datenbestand, sondern enthalten im Gegensatz zu nicht gel&ouml;schten Datens&auml;tzen lediglich ein Datum in einem Feld namens <b>GeloeschtAm<\/b>. <\/p>\n<p>Sie m&uuml;ssen also die <b>WHERE<\/b>-Klausel aller Datenherk&uuml;nfte, die sich auf eine betroffene Tabelle beziehen, um den Ausdruck <b>GeloeschtAm IS NULL <\/b>erweitern beziehungsweise &uuml;berhaupt erst eine entsprechende <b>WHERE<\/b>-Klausel anlegen.<\/p>\n<p>Das ist nat&uuml;rlich mit sehr viel Suchaufwand verbunden &#8211; vor allem, wenn Sie eine umfangreiche Anwendung nachtr&auml;glich um die hier vorgestellten Funktionen erweitern m&ouml;chten.<\/p>\n<p>Falls dieser Aufwand den Rahmen sprengen sollte, k&ouml;nnen Sie dies allerdings auch ganz einfach erledigen &#8211; allerdings auf Kosten einiger neuer Datenbankobjekte: Dazu benennen Sie einfach die Originaltabellen um, indem Sie ihnen einen Unterstrich (<b>_<\/b>) hinzuf&uuml;gen, eine <b>1 <\/b>anh&auml;ngen oder irgendeine andere mehr oder weniger sinnvolle &auml;nderung vornehmen.<\/p>\n<p>Aber Achtung: Vergessen Sie nicht, die Objektnamen-Autokorrektur zuvor abzuschalten, sonst ist der gew&uuml;nschte Effekt dahin &#8211; Access w&uuml;rde dann die Referenzen auf die betroffenen Elemente gleich mit &auml;ndern, soweit m&ouml;glich.<\/p>\n<p>Nat&uuml;rlich funktioniert danach nicht mehr viel, denn Formulare, Berichte oder Steuerelemente und auch die im VBA-Code enthaltenen Abfragen finden die Tabellen nun einfach nicht mehr.<\/p>\n<p>Aber jetzt kommt der Clou: Erstellen Sie einfach f&uuml;r jede betroffene Tabelle eine Abfrage, die alle Felder der jeweiligen Tabelle enth&auml;lt (einfach alle Felder markieren und in das Entwurfsraster ziehen) und genauso hei&szlig;t wie die alte Tabelle. Den feinen Unterschied macht das Feld <b>GeloeschtAm<\/b>, dem Sie das Kriterium <b>IS NULL <\/b>zuweisen.<\/p>\n<p>Wenn ein Objekt nun nach <b>tblKunden <\/b>sucht, findet es die Abfrage <b>tblKunden<\/b>, die wiederum alle Daten der Tabelle <b>tblKunden1<\/b> (ehemals <b>tblKunden<\/b>) liefert. Je nach Umfang der Datenbank spart dies Unmengen Zeit, denn Sie brauchen so keine einzige Datenherkunft anzupassen.<\/p>\n<p><b>Zusammenfassung und Ausblick<\/b><\/p>\n<p>&auml;nderungsinformationen speichern k&ouml;nnen Sie auch ohne umfangreiche &auml;nderungen an der Datenbank.<\/p>\n<p>Mit der hier vorgestellten Methode k&ouml;nnen Sie die meisten F&auml;lle mit minimalem Aufwand abfangen: Die notwendigen Felder legen Sie mit einem einfachen Funktionsaufruf an, das &auml;ndern gebundener Daten in Formularen erledigen Sie durch das Hinzuf&uuml;gen zweier kleiner Ereignisprozeduren und auch f&uuml;r die <b>Execute<\/b>-Methode des <b>Database<\/b>-Objekts haben wir einen sch&ouml;nen Workaround gefunden, bei dem Sie auch noch ganz normal die <b>DELETE<\/b>-Anweisung weiterverwenden k&ouml;nnen, ohne dass diese tats&auml;chlich Daten l&ouml;scht. Und mit dem Trick zum Herausfiltern der als gel&ouml;scht markierten Datens&auml;tze ist auch die letzte H&uuml;rde schnell genommen.<\/p>\n<h3>Downloads zu diesem Beitrag<\/h3>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>Aenderungshistorie.mdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/{AB912CE8-BB85-4E70-8792-DF67F4CC4E52}\/aiu_672.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wenn mehrere Benutzer mit den Daten einer Datenbank arbeiten, m&ouml;chten Sie f&uuml;r die relevanten Tabellen vielleicht nachhalten, wer wann welchen Datensatz angelegt, bearbeitet oder gel&ouml;scht hat. Dazu sind zwei Schritte n&ouml;tig: Das Hinzuf&uuml;gen der Felder zum Speichern dieser Informationen in den relevanten Tabellen und das Eintragen dieser Informationen, wenn es soweit ist. Dieser Beitrag zeigt, wie das ganz einfach geht. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662009,66042009,44000021],"tags":[],"class_list":["post-55000672","post","type-post","status-publish","format-standard","hentry","category-662009","category-66042009","category-Tabellen_und_Datenmodellierung"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v20.9 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>&Auml;nderungsdaten protokollieren - Access im Unternehmen<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"&Auml;nderungsdaten protokollieren\" \/>\n<meta property=\"og:description\" content=\"Wenn mehrere Benutzer mit den Daten einer Datenbank arbeiten, m&ouml;chten Sie f&uuml;r die relevanten Tabellen vielleicht nachhalten, wer wann welchen Datensatz angelegt, bearbeitet oder gel&ouml;scht hat. Dazu sind zwei Schritte n&ouml;tig: Das Hinzuf&uuml;gen der Felder zum Speichern dieser Informationen in den relevanten Tabellen und das Eintragen dieser Informationen, wenn es soweit ist. Dieser Beitrag zeigt, wie das ganz einfach geht.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/\" \/>\n<meta property=\"og:site_name\" content=\"Access im Unternehmen\" \/>\n<meta property=\"article:published_time\" content=\"2020-05-22T22:16:14+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e\" \/>\n<meta name=\"author\" content=\"Andr\u00e9 Minhorst\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andr\u00e9 Minhorst\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"21\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/\"},\"author\":{\"name\":\"Andr\u00e9 Minhorst\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\"},\"headline\":\"&Auml;nderungsdaten protokollieren\",\"datePublished\":\"2020-05-22T22:16:14+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/\"},\"wordCount\":3565,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg08.met.vgwort.de\\\/na\\\/6b224f86ad3246b68aef04ee7e358a7e\",\"articleSection\":[\"2009\",\"4\\\/2009\",\"Tabellen und Datenmodellierung\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/\",\"name\":\"&Auml;nderungsdaten protokollieren - Access im Unternehmen\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg08.met.vgwort.de\\\/na\\\/6b224f86ad3246b68aef04ee7e358a7e\",\"datePublished\":\"2020-05-22T22:16:14+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#primaryimage\",\"url\":\"http:\\\/\\\/vg08.met.vgwort.de\\\/na\\\/6b224f86ad3246b68aef04ee7e358a7e\",\"contentUrl\":\"http:\\\/\\\/vg08.met.vgwort.de\\\/na\\\/6b224f86ad3246b68aef04ee7e358a7e\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Aenderungsdaten_protokollieren\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"&Auml;nderungsdaten protokollieren\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"name\":\"Access im Unternehmen\",\"description\":\"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access\",\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/access-im-unternehmen.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\",\"name\":\"Andr\u00e9 Minhorst Verlag\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"contentUrl\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"width\":370,\"height\":111,\"caption\":\"Andr\u00e9 Minhorst Verlag\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\",\"name\":\"Andr\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"caption\":\"Andr\u00e9 Minhorst\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"&Auml;nderungsdaten protokollieren - Access im Unternehmen","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/","og_locale":"de_DE","og_type":"article","og_title":"&Auml;nderungsdaten protokollieren","og_description":"Wenn mehrere Benutzer mit den Daten einer Datenbank arbeiten, m&ouml;chten Sie f&uuml;r die relevanten Tabellen vielleicht nachhalten, wer wann welchen Datensatz angelegt, bearbeitet oder gel&ouml;scht hat. Dazu sind zwei Schritte n&ouml;tig: Das Hinzuf&uuml;gen der Felder zum Speichern dieser Informationen in den relevanten Tabellen und das Eintragen dieser Informationen, wenn es soweit ist. Dieser Beitrag zeigt, wie das ganz einfach geht.","og_url":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/","og_site_name":"Access im Unternehmen","article_published_time":"2020-05-22T22:16:14+00:00","og_image":[{"url":"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e","type":"","width":"","height":""}],"author":"Andr\u00e9 Minhorst","twitter_card":"summary_large_image","twitter_misc":{"Verfasst von":"Andr\u00e9 Minhorst","Gesch\u00e4tzte Lesezeit":"21\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#article","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/"},"author":{"name":"Andr\u00e9 Minhorst","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f"},"headline":"&Auml;nderungsdaten protokollieren","datePublished":"2020-05-22T22:16:14+00:00","mainEntityOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/"},"wordCount":3565,"commentCount":0,"publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#primaryimage"},"thumbnailUrl":"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e","articleSection":["2009","4\/2009","Tabellen und Datenmodellierung"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/","url":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/","name":"&Auml;nderungsdaten protokollieren - Access im Unternehmen","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#primaryimage"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#primaryimage"},"thumbnailUrl":"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e","datePublished":"2020-05-22T22:16:14+00:00","breadcrumb":{"@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#primaryimage","url":"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e","contentUrl":"http:\/\/vg08.met.vgwort.de\/na\/6b224f86ad3246b68aef04ee7e358a7e"},{"@type":"BreadcrumbList","@id":"https:\/\/access-im-unternehmen.de\/Aenderungsdaten_protokollieren\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/access-im-unternehmen.de\/"},{"@type":"ListItem","position":2,"name":"&Auml;nderungsdaten protokollieren"}]},{"@type":"WebSite","@id":"https:\/\/access-im-unternehmen.de\/#website","url":"https:\/\/access-im-unternehmen.de\/","name":"Access im Unternehmen","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access","publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/access-im-unternehmen.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/access-im-unternehmen.de\/#organization","name":"Andr\u00e9 Minhorst Verlag","url":"https:\/\/access-im-unternehmen.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/","url":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","contentUrl":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","width":370,"height":111,"caption":"Andr\u00e9 Minhorst Verlag"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","caption":"Andr\u00e9 Minhorst"}}]}},"_links":{"self":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55000672","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/comments?post=55000672"}],"version-history":[{"count":0,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55000672\/revisions"}],"wp:attachment":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/media?parent=55000672"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/categories?post=55000672"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/tags?post=55000672"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}