Zusammenfassung
Lernen Sie die Lösung zu den häufigsten VBA-Problemen kennen.
Techniken
VBA, Komprimieren, MsgBox, MDE, Diagramme
Voraussetzungen
Access 97 und höher
Beispieldateien
FAQ13_A97_FE.mdb, FAQ13_A97_BE.mdb FAQ13_A00_FE.mdb, FAQ13_A00_BE.mdb
Karl Donaubauer, Wien
In der Access-FAQ von Karl Donaubauer (www.donkarl.com) finden Sie die meistgestellten Fragen und Anworten zum Thema Microsoft Access. In dieser Beitragsreihe stellt Karl Donaubauer die wichtigsten Einträge im Detail vor und zeigt Ihnen entsprechende Lösungen anhand praxisnaher Beispiele. Im dreizehnten Teil werden Lösungen für weitere häufige Probleme und Bugs bei der VBA-Programmierung vorgestellt.
In vielen Situationen muss man ermitteln, ob und wie viele Datensätze eine Tabelle oder Abfrage oder ein Recordset enthält. Dafür existieren mehrere Ansätze mit unterschiedlichen Vor- und Nachteilen. Kurz und schnell geschrieben ist die Domänenaggregatfunktion DomAnzahl (DCount in VBA):
DCount("*", "Tabelle")
Domänenaggregatfunktionen sind allgemein als langsam verschrieen. Das stimmt auch in manchen Fällen, vor allem bei der Verwendung in Abfragen, weil die JET-Datenbank-Engine solche Zugriffe nicht optimieren kann, keinen Ausführungsplan verwenden kann und daher alle Datensätze der Domäne ziehen muss. In vielen Fällen lassen sie sich allerdings durchaus brauchbar einsetzen. Testen Sie am besten in der konkreten Anwendung und Umgebung und vor allem mit den zu erwartenden Datenmengen. Ein großer Vorteil der D-Funktionen ist die relativ einfache und kurze Syntax. Ein anderer ist ihre universelle Einsetzbarkeit, sowohl in Steuerelementinhalten wie in Abfragen oder in VBA.
Eine andere Möglichkeit ist die Verwendung der SQL-Aggregatfunktion Count. Der SQL-String einer Abfrage könnte so aussehen:
SELECT Count(*) AS [AnzahlDS] FROM Tabelle;
Diese Methode ist schneller als DCount. Wenn Sie das Ergebnis allerdings außerhalb der Abfrage benötigen, müssen Sie es zum Beispiel mit einem DAO-Recordset abrufen.
DAO bietet zum Zählen der Datensätze die Eigenschaft RecordCount. Der Code im Beispielformular frmDSZaehlen sieht so aus:
Dim db As DAO.Database Dim rs As DAO.Recordset Set db = CurrentDb Set rs = db.OpenRecordset("SELECT * FROM tblArtikel", dbOpenDynaset) If Not rs.BOF Then rs.MoveLast Me!txtRecordCountMit = rs.RecordCount End If
Vor der Verwendung von RecordCount ist es unbedingt notwendig, mit der MoveLast-Methode zum letzten Datensatz des Recordsets zu springen, damit die richtige Anzahl von Datensätzen ermittelt wird. Der Wechsel zum letzten Datensatz braucht bei großen Recordsets natürlich einige Zeit und sollte deshalb nur durchgeführt werden, wenn Sie wirklich sofort die genaue Datensatzanzahl brauchen. Ohne MoveLast ergibt RecordCount 1, wenn Datensätze vorhanden sind, und 0, wenn keine vorhanden sind (siehe Bild 1).
Bild 1: Varianten zum Zählen von Datensätzen
Wenn das Recordset leer ist, erzeugt MoveLast den Laufzeitfehler 3021 „Kein Aktueller Datensatz“. Deshalb wird im Beispielcode vor dem MoveLast auf BOF geprüft.
Wenn ein DAO-Recordset geöffnet wird, gibt es zwei mögliche Positionen: Falls Datensätze vorhanden sind, steht der Zeiger zuverlässig auf dem ersten Datensatz. Die OpenRecordset-Methode hat nämlich ein MoveFirst eingebaut. Ein anfängliches, ausdrückliches MoveFirst im Code, wie man es oft sieht, ist daher nicht notwendig. Falls hingegen keine Datensätze vorhanden sind, sind sowohl die Eigenschaften BOF als auch EOF True. Sie können also wahlweise das eine oder das andere prüfen. Die beliebte Prüfung auf beides ist daher wiederum nicht notwendig.
Wenn es nur darum geht, zu erfahren, ob ein Recordset überhaupt Datensätze hat, und andernfalls zum Beispiel das Durchlaufen des Recordsets ganz zu unterlassen, dann reicht:
If rs.BOF Then Exit Sub
Die schnellste Suche in DAO ist mit der Seek-Methode möglich. Der Grund ist, dass anders als bei FindFirst ein Index verwendet werden kann, der die Suche wesentlich beschleunigt. Je größer die Tabelle, desto größer der Geschwindigkeitsvorteil. Die Seek-Methode hat jedoch den Nachteil, dass sie nur bei Recordsets vom Typ Table anwendbar ist. Dieser ist wiederum nur für Tabellen verfügbar, die sich in der aktuellen Datenbank befinden.
Wenn Sie OpenRecordset auf eine Tabelle in der aktuellen Datenbank anwenden und dabei den Parameter für den Typ nicht angeben, wird automatisch der Typ Table verwendet.
Bei eingebundenen Tabellen hingegen, die der Regelfall in professionell gestalteten Anwendungen sind, ist der Standardtyp Dynaset und der Typ Table nicht verfügbar. Wie die Seek-Methode im Normalfall funktioniert, zeigt der Code der ersten Schaltfläche im Beispielformular frmSeek:
Dim db As dao.Database Dim rs As dao.Recordset Set db = CurrentDb Set rs = db.OpenRecordset("tblArtikel", dbOpenTable) rs.Index = "PrimaryKey" rs.Seek "=", "13" If Not rs.NoMatch Then Me!txtSeekIntern = rs!Bezeichnung End If
Es wird also ein Recordset vom Typ Table geöffnet, die Index-Eigenschaft des Recordsets auf den Namen eines in der Tabelle vorhandenen Indexes gesetzt und dann bei Seek der Operator und der Vergleichsausdruck angegeben.
Wenn man den gleichen Code für eine eingebundene Tabelle verwendet, erzeugt das den Laufzeitfehler „3219: unzulässige Operation“, weil hier der Typ Table eben nicht zulässig ist. Lässt man die Typangabe dbOpenTable weg, erzeugt der Code wegen des Seek den etwas aussagekräftigeren Laufzeitfehler „3251: Operation wird für diesen Objekttyp nicht unterstützt!“.
Die Lösung ist, als Database nicht Currentdb zu verwenden, sondern mit Opendatabase die externe Datenbank für den Zugriff zu öffnen und dann wieder ein Recordset vom Typ Table zu verwenden:
Set db = DBEngine.Workspaces(0). _ OpenDatabase("externe DB") Set rs = db.OpenRecordset _ ("externe Tabelle", dbOpenTable)
Auch diese Methode wird in der Beispieldatenbank demonstriert.
JET-Datenbanken haben leider die Tendenz, sich auch ohne Datenzuwachs aufzublähen. Die Ursachen sind vielfältig: Bewusst gelöschte Datensätze und Objekte, temporäre Objekte, die Access zur Verarbeitung braucht, diverse Bugs und Probleme von DAO und schlecht kompilierter Code. Bei den meisten dieser Gründe hilft das Komprimieren der Datenbank. Wenn Sie das Komprimieren automatisch durchführen lassen möchten, so haben Sie mit Access 97 schlechte Karten. Das einzig einfache automatisierte Komprimieren ist in Access 97 mit der Sendkeys-Methode möglich:
SendKeys "%xdk"
Mit diesem Code wird die Tastenkombination aufgerufen, die den normalen Menüpunkt zum Komprimieren aufruft: Extras/Datenbank-Dienstprogramme/Datenbank komprimieren.
Leider hat Sendkeys den bekannten Bug, dass es gerne, wenn auch nicht immer, die NumLock-Taste deaktiviert. Zudem ist es oft unmöglich, die Umgebung beim Anwender zu kontrollieren und daher zu wissen, ob der Menüpunkt gerade sichtbar und zugänglich ist und was eine bestimmte Tastenkombination bei den gerade offenen Programmen und Einstellungen am Arbeitsplatz auslöst. Deshalb ist eine grundsätzliche Skepsis gegenüber Sendkeys-Notlösungen angebracht.
Eine andere Möglichkeit in Access 97 ist die Verwendung des TSI SOON (Shut One, Open New) database add-in von Michael Kaplan (http://www.trigeminal.com/utility.asp). Dieses Add-In muss natürlich installiert, weitergegeben und aufgerufen werden.
Mit Access 2000 hat Microsoft endlich eine Möglichkeit zum automatisierten Komprimieren eingebaut. Im Menü/Extras/Optionen/Allgemein lässt sich einstellen, dass die Datenbank beim Schließen komprimiert werden soll.
Wenn Sie die Datenbank hingegen während des laufenden Betriebes komprimieren lassen wollen, so gibt es ebenfalls ab Access 2000 eine wesentlich stabilere und sicherere Methode, um den Menüpunkt für das Komprimieren per VBA aufzurufen, nämlich über die Standardaktion des entsprechenden Commandbar-Objektes (in einer Zeile):
CommandBars("MenuBar").Controls("Extras").Controls("Datenbank-Dienstprogramme").Controls("Datenbank komprimieren und reparieren...").accDoDefaultAction
Völlig unterschiedlich ist das Vorgehen zum automatisierten Komprimieren einer anderen Datenbank. Meist geht es darum, das Daten-Backend der aktuellen Frontend-mdb zu komprimieren. Die wichtigste Voraussetzung dafür ist, dass die andere MDB nicht geöffnet ist. Das heißt, Sie müssen dafür sorgen, dass beim Komprimieren nicht durch offene Formulare oder andere Datenbankobjekte auf Daten aus eingebundenen Tabellen zugegriffen wird. Ebenso dürfen keine Codeobjekte wie etwa Recordsets geöffnet sein, die auf Tabellen der zu komprimierenden Datenbank basieren. Quellcode 1 zeigt den für das Komprimieren nötigen Code.
Quellcode 1: Komprimieren einer Datenbank
Public Sub procCompactOtherDB(strPath As String) On Error GoTo myError DoCmd.Hourglass True Dim strFile As String Dim varDummyPath As Variant Dim varDummyFile As Variant strFile = Dir(strPath) varDummyPath = Left$(strPath, Len(strPath) - Len(strFile)) varDummyFile = varDummyPath & "Dummy.mdb" DBEngine.CompactDatabase strPath, varDummyFile Kill strPath Name varDummyFile As strPath myExit: DoCmd.Hourglass False Exit Sub myError: Select Case Err.Number Case 3005, 3024, 53, 3044, 76 MsgBox "Datenbank nicht gefunden.", vbOKOnly Case 3196 MsgBox "Datenbank in Benutzung.", vbOKOnly Case Else MsgBox "Ausnahme Nr. " & Err.Number _ & ". Das heißt: " & Err.Description End Select Resume myExit End Sub
Es handelt sich um eine Public Sub, die Sie am besten in ein Standardmodul kopieren. Der Aufruf erfolgt mit:
procCompactOtherDB "PfadUndNameDerZuKomprimierenden.mdb"
Der Kern des Codes ist die Methode CompactDatabase. Diese Methode benötigt als Parameter Pfad und Name der Quelldatenbank und einen unterschiedlichen Namen für die komprimierte Zieldatenbank.
Das ist an sich fein, denn damit kann man CompactDatabase sehr einfach zum Erzeugen einer anders benannten und noch dazu komprimierten Backup-Kopie verwenden. In der Praxis will man aber meistens, dass das Backend nach dem Komprimieren genauso heißt wie vorher. Deshalb wird in Quellcode 1 eine Dummy.mdb im Ordner der Quelldatenbank als Zieldatenbank angegeben. Nach dem Komprimieren wird die unkomprimierte Quelldatenbank mit der Kill-Anweisung gelöscht und die komprimierte Dummy.mdb erhält mithilfe der Name-Anweisung den Namen der ursprünglichen Quelldatenbank. In der Fehlerbehandlung werden die häufigsten Fehlernummern abgefragt, die in verschiedenen Access-Versionen bei diesen Vorgängen auftreten können.
Ein großes Problem von Access war und ist die Sicherheit im Sinne des Schutzes der Daten und Anwendung vor neugierigen Blicken. Ich werde im Laufe des Artikels noch einmal auf diese Problematik eingehen. Einer der vielen kleinen Tricks, die gerne verwendet werden, um zumindest unbedarfte Anwender vom Einsehen der Datenbankobjekte abzuhalten, ist das Ausblenden des Datenbankfensters.
Das Ausblenden und Einblenden des Datenbankfensters können Sie auch sehr einfach per Code regeln. Zum Einblenden brauchen Sie nur mit der Methode SelectObject ein Objekt im Datenbankfenster aufzurufen. Da der Objektname der Methode optional ist, müssen Sie nicht einmal ein Objekt angeben, sondern es reicht der Objekttyp:
DoCmd.SelectObject acTable, , True
Wichtig ist hingegen, dass der dritte Parameter auf True steht, damit das Phantomobjekt auch wirklich im Datenbankfenster ausgewählt wird.
Zum Ausblenden wird genau die gleiche Zeile verwendet, gefolgt vom RunCommand-Befehl, der dem Menüpunkt Fenster/Ausblenden entspricht:
DoCmd.SelectObject acTable, , True RunCommand acCmdWindowHide
Quellcode 2: Formatieren von Meldungsfenstern
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