Aktuelle Datenbankversion ermitteln

Es gibt verschiedene Gründe, um Kopien einer Datenbank anzulegen. Das Herstellen einer Sicherungskopie ist wohl der am meisten verbreitete Grund. Das ist sinnvoll, aber es kann dabei zu Problemen kommen, wenn man nicht achtsam ist: Dann arbeitet man auf einmal in der Sicherheitskopie weiter und wundert sich, wenn man anschließend die Originaldatenbank öffnet und die Funktionen, die man neu hinzuprogrammiert, nicht mehr findet. Oder man testet mit einer Datenbankdatei auf einer virtuellen Maschine und fügt dort Anpassungen hinzu. Auch hier kann es zu einem ähnlichen Durcheinander kommen. Um in einem solchen Fall die aktuelle Version zu finden, reicht es nicht, sich das Änderungsdatum der Datei anzusehen. Warum das nicht reicht und wie wir die aktuellere Datenbank zuverlässig finden, zeigen wir in diesem Beitrag.

Die Ausgangssituation lässt sich auch wie folgt zusammenfassen: Uns liegen zwei Datenbankdateien gleichen Namens vor, von denen wir nicht mehr wissen, welche den aktuellsten Bearbeitungsstand aufweist. Und leider haben wir auch noch soeben beide Versionen geöffnet, um zu schauen, ob wir so herausfinden können, welche die aktuelle Version ist. Damit haben wir einen wichtigen Marker zerstört – nämlich das Änderungsdatum der Datei.

Daran hätten wir zumindest erkennen können, welche Version wir zuletzt geöffnet haben. Allerdings wird bereits beim Öffnen einer Access-Anwendung das Änderungsdatum auf das aktuelle Datum eingestellt – wir brauchen dazu gar keine Änderungen am Entwurf oder an den Daten vorzunehmen.

Nun haben wir also zwei verschiedene Versionen einer Datenbank vorliegen und benötigen Kriterien, um herauszufinden, an welcher wir zuletzt gearbeitet haben.

Diese lauten beispielsweise wie folgt:

  • Gibt es in einer der beiden Datenbanken neue, die es in der anderen Datenbank nicht gibt?
  • Wurden aus einer der Datenbanken vielleicht sogar Objekte gelöscht?
  • Wie lautet das letzte Bearbeitungsdatum der Objekte der Datenbank?

Wenn wir diese Kriterien untersuchen, sollten wir annähernd herausfinden können, welche der beiden Datenbankdateien den aktuelleren Stand aufweist. Das ist zumindest dann der Fall, wenn wir nur an einer der beiden Änderungen seit einem bestimmten Zeitpunkt vorgenommen haben.

Wenn wir die Datenbanken abwechselnd geöffnet und Änderungen an verschiedenen (oder sogar den gleichen) Stellen vorgenommen haben, ist das zwar unangenehm, weil wir die Änderungen dann zusammenführen müssen. Wir wissen dann aber zumindest, warum bestimmte Änderungen, von denen wir sicher waren, dass wir sie durchgeführt haben, nicht mehr vorhanden sind.

Änderungszeitpunkt von Objekten ermitteln

Den Änderungszeitpunkt eines Objekts können wir neben dem Erstellungszeitpunkt über die Benutzeroberfläche von Access ermitteln. Dazu klicken wir mit der rechten Maustaste in die Titelleiste des Navigationsbereichs von Access und wählen dort den Eintrag Anzeigen nach|Details aus. Danach sehen wir die gewünschten Informationen zu jedem Eintrag (siehe Bild 1).

Änderungszeitpunkt von Objekten ermitteln

Bild 1: Änderungszeitpunkt von Objekten ermitteln

Wenn wir Datenbanken mit so wenigen Objekten wie die aus dem Screenshot vergleichen wollen, brauchen wir dafür keine eigene Anwendung zu entwickeln. Aber in der Regel ist die Anzahl der enthaltenen Objekte größer und ein manueller Abgleich macht schnell keinen Spaß mehr.

Also machen wir uns auf die Suche nach dem Speicherort dieser Informationen und stoßen dabei schnell auf die Systemtabelle MSysObjects. Systemtabellen wie diese sind standardmäßig ausgeblendet. Wir holen diese hervor, indem wir wieder mit der rechten Maustaste auf die Titelleiste des Navigationsbereichs klicken und nun den Eintrag Navigationsoptionen… auswählen. Es erscheint der Dialog Navigationsoptionen, wo wir im Bereich Anzeigeoptionen die Optionen Ausgeblendete Objekte anzeigen und Systemobjekte anzeigen aktivieren.

Diese Tabelle liefert für alle Datenbankobjekte wie Tabellen, Abfragen, Formulare, Berichte, Makros und VBA-Module jeweils einen Eintrag (und für einige weitere Objekttypen, die hier nicht von Interesse sind). Dieser enthält neben dem Objekttyp und dem Namen auch zwei Felder namens DateCreated und DateUpdated. Damit haben wir bereits alle Informationen, die wir benötigen. Wir müssen sie nur noch zusammenführen und abgleichen.

Objektedaten in neue Datenbank importieren

Dazu erstellen wir eine neue Datenbank namens AktuellereDatenbankFinden.accdb. In diese wollen wir zuerst die Tabelle MSysObjects der beiden Versionen der Datenbank importieren. Dazu benötigen wir bereits ein Formular, denn wir wollen die Auswahl der Datenbankdateien so komfortabel wie möglich gestalten – in diesem Fall mit entsprechenden Dateiauswahl-Dialogen.

Nach der Auswahl der abzugleichenden Datenbanken wollen wir mit einer Schaltfläche einen Vorgang starten, bei dem die relevanten Daten für alle in den beiden Datenbanken enthaltenen Objekte in eine zusammenfassende Tabelle geschrieben werden.

Diese heißt tblObjekte und ihr Entwurf sieht wie in Bild 2 aus. Neben dem Objektnamen speichern wir darin den Objekttyp sowie das Änderungsdatum für das Objekt in der ersten und in der zweiten Datenbank. Wenn das Objekt nur in einer der beiden Datenbanken vorhanden ist, schreiben wir das Datum nur in das entsprechende Feld.

Entwurf der Tabelle zum Speichern der Änderungsdaten der Objekte

Bild 2: Entwurf der Tabelle zum Speichern der Änderungsdaten der Objekte

Formular zum Anzeigen der Änderungsdaten

Das Formular zur Auswahl der zu untersuchenden Dateien und zum Anzeigen der relevanten Daten legen wir unter dem Namen frmVergleichen an. Diesem fügen wir zunächst zwei Textfelder namens txtVersion1 und txtVersion2 hinzu. Daneben platzieren wir zwei Schaltflächen.

Diese statten wir jeweils mit einem Ordner-Icon aus und stellen die Eigenschaften Hintergrundart und Rahmenart auf Transparent ein. Das Formular sieht anschließend wie in Bild 3 aus.

Felder zum Auswählen der zu vergleichenden Dateien

Bild 3: Felder zum Auswählen der zu vergleichenden Dateien

Für die Ereignisprozeduren der beiden Schaltflächen, die durch das Ereignis Beim Klicken ausgelöst werden, hinterlegen wir die folgenden Prozeduren:

Private Sub cmdDateiOeffnen1_Click()
     Me!txtVersion1 = DateiOeffnen
End Sub
Private Sub cmdDateiOeffnen2_Click()
     Me!txtVersion2 = DateiOeffnen
End Sub

Beide nutzen die gleiche Funktion, um den Pfad zu der zu untersuchenden Datei zu ermitteln:

Private Function DateiOeffnen() As String
     Dim objFiledialog As Office.FileDialog
     Dim varFilename As Variant
     Set objFiledialog = _
         Application.FileDialog(msoFileDialogFilePicker)
     objFiledialog.InitialFileName = CurrentProject.Path
     If objFiledialog.Show = True Then
         DateiOeffnen = objFiledialog.SelectedItems(1)
     End If
End Function

Um das FileDialog-Objekt zu nutzen, benötigen wir einen Verweis auf die Bibliothek Microsoft Office 16.0 Object Library, den wir mit dem Verweise-Dialog aus Bild 4 hinzufügen (VBA-Editor, Menüeintrag Extras|Verweise). Für die beiden Textfelder stellen wir die Eigenschaft Horizontaler Anker auf Beide ein, damit sich diese an die Formularbreite anpassen.

Hinzufügen eines Verweises auf die Office-Bibliothek

Bild 4: Hinzufügen eines Verweises auf die Office-Bibliothek

Unterformular zum Anpassen der Ergebnisse

Bevor wir die Funktion zum Abgleichen der Unterschiede anlegen, fügen wir dem Formular ein Unterformular zum Anzeigen der Ergebnisse hinzu. Das Unterformular heißt sfmVergleichen und verwendet die Tabelle tblObjekte als Datensatzquelle. Wir fügen alle Felder dieser Tabelle zum Formular hinzu und stellen seine Eigenschaft Standardansicht auf Datenblatt ein (siehe Bild 5).

Unterformular der Anwendung

Bild 5: Unterformular der Anwendung

Danach fügen wir das Formular samt einer weiteren Schaltfläche namens cmdVergleichen zum Hauptformular hinzu (siehe Bild 6). Für das hinzugefügte Unterformular stellen wir die beiden Eigenschaften Horizontaler Anker und Vertikaler Anker auf Beide ein.

Das Hauptformular der Anwendung in der Entwurfsansicht

Bild 6: Das Hauptformular der Anwendung in der Entwurfsansicht

Einlesen der Objektdaten der Datenbanken

Im nächsten Schritt fügen wir der Schaltfläche cmdVergleichen eine Prozedur für das Ereignis Beim Klicken hinzu (siehe Listing 1).

Private Sub cmdVergleichen_Click()
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim lngObjektID As Long
     Dim strObjekttyp As String
     Dim strWhere As String
     Set db = CurrentDb
     On Error Resume Next
     db.Execute "DELETE FROM tblObjekte", dbFailOnError
     db.Execute "DROP TABLE tblObjects1", dbFailOnError
     db.Execute "DROP TABLE tblObjects2", dbFailOnError
     On Error GoTo 0
     DoCmd.TransferDatabase acImport, "Microsoft Access", Me.txtVersion1, acTable, "MSysObjects", "tblObjects1"
     DoCmd.TransferDatabase acImport, "Microsoft Access", Me.txtVersion2, acTable, "MSysObjects", "tblObjects2"
     strWhere = " WHERE Type IN (1, 4, 5, 6, -32761, -32764, -32766, -32768) AND NOT Name LIKE ''~*'' " _
         & "AND NOT Name LIKE ''MSys*'' AND NOT Name LIKE ''f_*''"
     Set rst = db.OpenRecordset("SELECT * FROM tblObjects1" & strWhere, dbOpenDynaset)
     Do While Not rst.EOF
         strObjekttyp = ObjekttypErmitteln(rst!Type)
         db.Execute "INSERT INTO tblObjekte(Objektname, Objekttyp, Geaendert1) VALUES(''" & rst!Name & "'', ''" _
             & strObjekttyp & "'', " & ISODatum(rst!DateUpdate) & ")", dbFailOnError
         rst.MoveNext
     Loop
     Set rst = db.OpenRecordset("SELECT * FROM tblObjects2" & strWhere, dbOpenDynaset)
     Do While Not rst.EOF
         lngObjektID = Nz(DLookup("ObjektID", "tblObjekte", "Objektname = ''" & rst!Name & "''"), 0)
         If lngObjektID = 0 Then
             strObjekttyp = ObjekttypErmitteln(rst!Type)
             db.Execute "INSERT INTO tblObjekte(Objektname, Objekttyp, Geaendert2) VALUES(''" & rst!Name & "'', ''" _
                 & strObjekttyp & "'', " & ISODatum(rst!DateUpdate) & ")", dbFailOnError
         Else
             db.Execute "UPDATE tblObjekte SET Geaendert2 = " & ISODatum(rst!DateUpdate) & " WHERE ObjektID = " _
                 & lngObjektID, dbFailOnError
         End If 
         rst.MoveNext
     Loop
     Me!sfmVergleichen.Form.Requery
End Sub

Listing 1: Prozedur zum Abgleichen der Änderungsdaten der Objekt der zu untersuchenden Datenbanken

Hier führen wir zuerst einige Aufräumarbeiten durch. Wir leeren die Tabelle tblObjekte, damit keine Daten aus vorherigen Untersuchungen mehr in der Tabelle sind. Außerdem löscht die Prozedur zwei Tabellen namens tblObjects1 und tblObjects2, die wir bisher noch gar nicht angelegt haben.

Genau aus diesem Grund erledigen wir diese Aufgabe auch bei deaktivierter eingebauter Fehlerbehandlung – der Versuch, eine nicht vorhandene Tabelle zu löschen, würde sonst einen Fehler auslösen.

Was aber sind das überhaupt für Tabellen? In diese kopieren wir die Daten der Systemtabellen namens MSysObjects der zu untersuchenden Datenbanken. Das erledigen wir mit der Methode TransferDatabase der DoCmd-Klasse. Damit diese einen Import durchführt, übergeben wir als ersten Parameter den Wert acImport. Der zweite Parameter gibt die Herkunftsanwendung der Daten an, der dritte den Typ des einzulesenden Objekts. Die letzten beiden Parameter legen fest, dass die Daten der Tabelle MSysObjects in die Tabelle tblObjects1 beziehungsweise tblObjects2 eingelesen werden sollen. Dieser Befehl erstellt die Tabellen auf Basis der Quelltabelle neu.

Abgleich der Änderungsdaten

Die folgenden Anweisungen legen die für den Abgleich der Änderungsdaten notwendigen Daten in der Tabelle tblObjekte an. Dabei wollen wir nur die Datensätze der Tabelle MSysObjects berücksichtigen, die eines der zu untersuchenden Objekte betreffen. Außerdem wollen wir Systemtabellen und temporäre Objekte oder im Hintergrund verwendete Objekte nicht berücksichtigen.

Dazu stellen wir ein Where-Kriterium zusammen, das nur die Objekttypen 1, 4, 5, 6, -32761, -32764, -32766 und -32768 berücksichtigt (mehr dazu weiter unten) und einige Objekte aufgrund ihres Objektnamens ausschließt (f_*, MSys* oder ~*).

Nun beginnen wir mit dem Einlesen der Daten aus der ersten zu untersuchenden Datenbank. Dazu erstellen wir ein Recordset auf Basis der Tabelle tblObjects1, welches das Kriterium aus strWhere berücksichtigt. Das Recordset enthält also nur die Objekte der Datenbank, die wir untersuchen wollen. Anschließend durchlaufen wir die Elemente des Recordsets in einer Do While-Schleife. Dabei rufen wir als Erstes eine Funktion namens ObjekttypErmitteln auf, der wir den Wert des Feldes Type übergeben. Die Funktion untersucht diesen Wert in einer Select Case-Bedingung und liefert einen Text zurück, welcher dem angegebenen Zahlenwert entspricht – bei dem Wert 1 beispielsweise den Text Tabelle:

Private Function ObjekttypErmitteln(lngObjekttyp As _
         Long) As String
     Select Case lngObjekttyp
         Case 1
             ObjekttypErmitteln = "Lokale Tabelle"
         Case 4
             ObjekttypErmitteln = "ODBC-Tabelle"
         Case 5
             ObjekttypErmitteln = "Abfrage"
         Case 6
             ObjekttypErmitteln = "Verknüpfte Tabelle"
         Case -32761
             ObjekttypErmitteln = "VBA-Modul"
         Case -32764
             ObjekttypErmitteln = "Bericht"
         Case -32766
             ObjekttypErmitteln = "Makro"
         Case -32768
             ObjekttypErmitteln = "Formular"
     End Select
End Function

Damit ausgestattet rufen wir die Execute-Methode des Database-Objekts der aktuellen Datenbank auf und übergeben dieser eine INSERT INTO-Anweisung, mit der wir einen neuen Datensatz zur Tabelle tblObjekte hinzufügen.

Dieser trägt die Werte für die Felder Objektname, Objekttyp und Geaendert1 ein, also das Änderungsdatum für das Objekt aus der ersten zu untersuchenden Datenbank.

Nach dem Durchlaufen der ersten Schleife sieht die Tabelle wie in Bild 7 aus. Für jedes relevante Objekt wurde ein Eintrag angelegt. Lediglich das Änderungsdatum für das gleichnamige Objekt in der zweiten zu untersuchenden Datenbank ist noch nicht vorhanden.

Die Tabelle tblObjekte nach dem Untersuchen der ersten Datenbank

Bild 7: Die Tabelle tblObjekte nach dem Untersuchen der ersten Datenbank

Dieses fügen wir nun hinzu. Dazu erstellen wir ein weiteres Recordset, das nun die Datensätze der Tabelle tblObjects2 mit den Daten aus der zweiten zu untersuchenden Datenbank enthält.

Während wir die Datensätze dieses Recordsets durchlaufen, ermitteln wir per DLookup den Primärschlüsselwert eines eventuell bereits vorhandenen Datensatzes für das aktuelle Objekt in der Tabelle tblObjekte. Hat lngObjektID anschließend den Wert 0, handelt es sich hierbei um ein Objekt, das nur in der zweiten, nicht aber in der ersten zu untersuchenden Datenbank vorhanden ist.

In diesem Fall legen wir einen neuen Datensatz in der Tabelle tblObjekte an, für den wir lediglich das Datum im Feld Geaendert2 eintragen – das Feld Geaendert1 bleibt leer. Ist lngObjektID nicht 0, ist das Objekt bereits in der Tabelle tblObjekte vorhanden und wir brauchen nur per UPDATE-Abfrage das Datum im Feld Geaendert2 zu ergänzen.

Nachdem wir so alle Objekte der beiden zu untersuchenden Datenbanken und ihre Änderungsdaten eingetragen haben, brauchen wir nur noch das Unterformular mit dem Inhalt der Tabelle tblObjekte zu aktualisieren.

Formular anpassen

Schauen wir uns nun das Ergebnis an, sehen wir, dass alle Daten wie gewünschte eingetragen wurden (siehe Bild 8). Allerdings können wir nicht besonders gut erkennen, ob irgendein Objekt in der einen oder anderen Datenbank ein aktuelleres Änderungsdatum aufweist. Das wird bei einer größeren Anzahl von Objekten nur noch schwieriger. Also passen wir das Unterformular noch so an, dass wir die Unterschiede besser erkennen können.

Anzeige der Änderungsdaten im Formular

Bild 8: Anzeige der Änderungsdaten im Formular

Unterschiede mit bedingter Formatierung

Dazu bietet sich der Einsatz der bedingten Formatierung an. Dazu zeigen wir das Formular in der Formularansicht an und klicken in die Datenblattansicht des Unterformulars. Im Ribbon finden wir nun unter Formulardatenblatt den Eintrag Formatierung|Bedingte Formatierung.

Klicken wir diesen Befehl an, nachdem wir das Feld Geaendert1 markiert haben, erscheint der Dialog Manager für Regeln zur bedingten Formatierung und zeigt unter Formatierungsregeln anzeigen für: direkt das gewünschte Feld an (siehe Bild 9).

Der Manager für die bedingte Formatierung

Bild 9: Der Manager für die bedingte Formatierung

Hier klicken wir auf Neue Regel und führen im Dialog Neue Formatierungsregel die folgenden Schritte durch:

  • Zweites Auswahlfeld auf Größer als einstellen
  • Für das Feld rechts daneben den Namen des zu vergleichenden Feldes in eckigen Klammern eingeben, hier [Geaendert2]
  • Hintergrundfarbe auf Grün einstellen (siehe Bild 10)
  • Einfügen der ersten Regel

    Bild 10: Einfügen der ersten Regel

Danach können wir den Dialog wieder schließen und legen dann eine zweite Regel an, diesmal für das Feld Geaendert2. Dazu wählen wir dieses Feld im Manager für Regeln zur bedingten Formatierung aus. Die Formatierungsregel ist fast genauso aufgebaut – nur dass wir hier mit dem Feld [Geaendert1] vergleichen.

Mit ein paar unterschiedlichen Änderungsdaten sieht das Unterformular nun wie in Bild 11 aus.

Farbige Markierung aktuellerer Elemente

Bild 11: Farbige Markierung aktuellerer Elemente

Es fehlt noch eine Markierung für Elemente, die nur in einer der beiden Datenbanken vorhanden sind. Diese wollen wir zur besseren Unterscheidbarkeit mit einem blauen Hintergrund versehen. Die Formatierungsregel bauen wir anders auf als die vorherigen. Wir stellen im ersten Auswahlfeld den Wert Ausdruck ist ein. Für das zweite Feld daneben geben wir folgenden Ausdruck an:

[Geaendert2] Ist Null Und [Geaendert1] Ist Nicht Null

Dadurch wird Geaendert1 blau markiert, wenn es nicht leer ist, aber Geaendert2 keinen Inhalt hat. Die vollständige Regel sehen Sie in Bild 12.

Regel für Elemente, die nur in einer Anwendung vorkommen

Bild 12: Regel für Elemente, die nur in einer Anwendung vorkommen

Für das Feld Geaendert2 formulieren wir die Regel genau andersherum. Das Ergebnis sieht, wenn jede Version der Datenbank ein Objekt aufweist, dass die andere Version nicht enthält, wie in Bild 13 aus.

Unterformular mit verschiedenen Unterschieden

Bild 13: Unterformular mit verschiedenen Unterschieden

Optimierungen der Benutzeroberfläche

Was noch auffällt, sind die überflüssigen Elemente im Hauptformular – diese entfernen wir durch Einstellen der Eigenschaften Datensatzmarkierer, Navigationsschaltflächen, Trennlinien und Bildlaufleisten auf den Wert Nein. Außerdem soll das Formular zentriert erscheinen. Dazu stellen wir die Eigenschaft Automatisch zentrieren auf Ja ein.

Umwandlung in ein Add-In

Der nächste Schritt ist die Umwandlung dieser Anwendung in ein Add-In, das wir über die Liste der Add-Ins im Ribbon starten können. Warum sollen wir diesen Aufwand betreiben?

Weil es nicht so häufig vorkommt, dass wir es einsetzen – und wenn wir es dann brauchen, wissen wir sicher, dass es über die Liste der Add-Ins in Access aufrufbar ist. Außerdem haben wir im Beitrag Access-Add-In per Knopfdruck erstellen (www.access-im-unternehmen.de/1427) ein Add-In programmiert, mit dem wir Datenbankanwendungen wie die vorliegende schnell in ein Add-In umwandeln können. Zeit, dieses Add-In einmal einzusetzen!

Dazu rufen wir das Add-In, das Sie im Download zu dem oben genannten Beitrag finden und installieren können, über das Add-In-Menü auf. Es erscheint ein Dialog, den wir wie in Bild 14 füllen.

Erstellen eines Add-Ins aus einer Datenbank

Bild 14: Erstellen eines Add-Ins aus einer Datenbank

Danach sind noch folgende Schritte nötig:

  • Einstellen der Dateiendung auf .accda
  • Ergänzen der Prozedur Autostart im Modul mdlAddIn um den Aufruf des Formulars frmVergleichen:
Public Function Autostart()
     DoCmd.OpenForm "frmVergleichen"
End Function

Danach rufen wir in Access den Add-In-Manager auf und fügen das neue Add-In über die Schaltfläche Neues hinzufügen… zur Liste der Add-Ins hinzu.

Anschließend finden Sie das Add-In im Ribbon und können es jederzeit verwenden, um die Unterschiede bezüglich der Änderungszeit der Objekte zweier Versionen einer Datenbank zu ermitteln.

Zusammenfassung und Ausblick

Dieser Beitrag ist aus meiner eigenen Praxis entstanden – ich hatte bei zwei Versionen einer Datenbank den Überblick verloren und wollte genau wissen, ob es Unterschiede bezüglich der Objekte gibt.

Ich überlegte zunächst, ob ich die Objekte komplett vergleichen wollte, aber das schien mir zu aufwendig und mir kam die Idee, einfach nur die Änderungsdaten der einzelnen Objekte zu vergleichen.

Dieser Beitrag zeigt, wie ich die Lösung entwickelt und daraus ein Add-In erstellt habe. Man könnte diese Lösung noch weiter aufbohren und den Code der einzelnen Objekte oder ihren Inhalt vergleichen.

Downloads zu diesem Beitrag

Enthaltene Beispieldateien:

AktuellereDatenbankfinden.accda

AktuellereDatenbankfinden.accdb

Beispiel_V1.accdb

Beispiel_V2.accdb

Download

Schreibe einen Kommentar