Access-FAQ

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

Schreibe einen Kommentar

Access-FAQ

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 vierten Teil lernen Sie die Lösungen zu den meistgenannten Problemen der Teilnehmer der deutschsprachigen Access-Newsgroups beim Ermitteln von Informationen zur Datenbank und zur Access-Version und beim Arbeiten mit Ausdrücken und Formeln kennen.

Für so manche Aufgabe ist es unerlässlich zu wissen, wie Pfad, Verzeichnis und Name der aktuellen Datenbank lauten. Sei es, um Operationen mit Dateien durchzuführen oder auch nur in einem Bericht den Pfad der Datenbank als Information mit auszudrucken.

Typische Verwendungsbeispiele sind das Wiedereinbinden von Tabellen oder das Anzeigen von Bildern, die im gleichen Ordner wie die Datenbank oder in einem Ordner relativ über oder unter dem Ordner mit der aktuellen Datenbank stehen.

In Access 2000 hat Microsoft der Access-Bibliothek das Objekt CurrentProject hinzugefügt, das diese Aufgabe gegenüber älteren Access-Versionen wesentlich vereinfacht. CurrentProject besitzt dazu die Eigenschaften FullName, Name und Path.

Fullname enthält den vollständigen Pfad und Namen der Datenbank. Name enthält nur den Namen der Datenbank samt Dateiendung. Path enthält nur den Pfad zur Datenbank.

Angenommen die aktuelle Datenbank heißt Kunden.mdb und befindet sich im Ordner C:\Projekte. Dann sehen die Inhalte der drei Eigenschaften wie in Tab. 1 aus.

Eigenschaft

Beispielwert

CurrentProject.FullName

C:\Projekte\Kunden.mdb

CurrentProject.Name

Kunden.mdb

CurrentProject.Path

C:\Projekte

Tab. 1: Eigenschaften von CurrentProject und Beispielwerte

Dieses Objekt und seine Eigenschaften können Sie nicht nur in VBA verwenden, sondern auch an der Access-Oberfläche. Sie können es zum Beispiel als Steuerelementinhalt eines Textfeldes in einem Formular oder Bericht eingeben:

=CurrentProject.FullName

Dann zeigt das Textfeld den vollständigen Pfad und Namen der aktuellen Datenbank an.

Microsoft hat jahrelang versucht, die Datenzugriffstechnik DAO durch das neuere ADO zu ersetzen. Das CurrentProject-Objekt ist ein Ausdruck dieser Bemühungen, denn die meisten seiner Eigenschaften und Methoden sind auf ADO ausgelegt. ADO konnte sich jedoch, was MDB-Dateien betrifft, bei der Mehrheit der Access-Entwickler nicht gegen DAO durchsetzen. Deshalb fristen auch die damit zusammenhängenden Neuerungen wie CurrentProject bei vielen Entwicklern ein Schattendasein.

Da Sie jedoch bei Verwendung von Access 97 noch gar kein CurrentProject-Objekt zur Verfügung haben, folgen nun ältere Möglichkeiten, Pfad und Name der aktuellen Datenbank zu erhalten.

Sie basieren auf der CurrentDb-Methode. Diese liefert einen Verweis auf die aktuelle Datenbank, deren Pfad und Name man deshalb mithilfe der Name-Eigenschaft erhält. Die Eigenschaft CurrentDb.Name enthält also beispielsweise den Wert C:\Projekte\Kunden.mdb.

Um daraus nur den Namen der Datenbank zu extrahieren, kann man die Dir-Funktion “missbrauchen”: Dir(CurrentDb.Name) gibt zum Beispiel den Wert Kunden.mdb aus.

Schwieriger ist das Extrahieren des Pfades. Dazu benötigen Sie die folgende Kombination von Funktionen: Left(CurrentDb.Name,Len(CurrentDb.Name)-Len(Dir(CurrentDb.Name))) ergibt C:\Projekte\.

Möchte man das gleiche Ergebnis wie mit CurrentProject.Path erhalten, also ohne den letzten Backslash, kann man das Ergebnis noch um eine Stelle verkürzen, wie folgender Aufruf zeigt: Left(CurrentDb.Name,Len(CurrentDb.Name)-Len(Dir(CurrentDb.Name))-1).

Hinweis

Die Beispiele und Quellcodes des vorliegenden Beitrags finden Sie auf der beiligenden Heft-CD unter den Namen FAQ4_97.mdb (Access 97) und FAQ4_00.mdb (Access 2000 und höher).

Diese Technik ist offensichtlich komplizierter. Dafür funktioniert sie in allen Access-Versionen ab Access 95. Sie werden auch kaum einen Unterschied in der Performance im Vergleich zu CurrentProject bemerken.

Die CurrentDb-Varianten sind ebenfalls im VBA-Code und an der Access-Oberfläche anwendbar. Zu beachten ist hier jedoch eine kleine Syntax-Falle. Wenn Sie zum Beispiel in einem Textfeld den Pfad der Datenbank anzeigen möchten, muss in der Eigenschaft Steuerelementinhalt das Semikolon statt des Kommas als Trennzeichen verwendet werden:

=Left(CurrentDb.Name;Len(CurrentDb.Name)-Len(Dir(CurrentDb.Name)))

Access übersetzt diese Eingabe dann selbsttätig in folgenden Ausdruck:

=Links(CurrentDb.Name;Länge(CurrentDb.Name)-Länge(Verz(CurrentDb.Name)))

ähnlich wie beim Verzeichnis und Namen der aktuellen Datenbank kann es auch nützlich sein, das Verzeichnis und die Version des aktuell verwendeten Access während der Laufzeit zu ermitteln.

Beide Informationen erhält man recht einfach mithilfe der SysCmd-Funktion und der passenden Aktionskonstanten dieser vielfältig einsetzbaren und nützlichen Funktion. Den Ordner der aktuell verwendeten msaccess.exe bekommt man mit dem folgenden Befehl:

SysCmd(acSysCmdAccessDir)

Die aktuell verwendete Access-Version erhält man mit dem Befehl:

SysCmd(acSysCmdAccessVer)

Dabei geht es wohlgemerkt nicht um die Version, mit der die Datenbank erstellt wurde oder deren Format sie hat, sondern um die Access-Version, mit der sie momentan geöffnet ist. Genauer gesagt erhält man als Ergebnis den vorderen Teil der Versionsnummer der msaccess.exe. Dieser ist entscheidend, um die Access-Version zu erkennen. Tab. 2 enthält eine Liste der möglichen Ergebnisse.

Bild 1: Formular frmDBAccess

Versionsnummer

Versionsbezeichnung

8.0

Access 97

9.0

Access 2000

10.0

Access 2002

11.0

Access 2003

Tab. 2: Versionsnummern und Versionsbezeichnungen von Access

Bild 1 zeigt das Formular frmDBAccess in der Beispiel-Datenbank, in dem Sie Ausdrücke und VBA-Code finden, um sowohl Pfad und Name der Datenbank als auch Pfad und Version von Access zu ermitteln und anzuzeigen.

Eine Aufgabe, die in fast jeder Datenbank mehrfach vorkommt, ist das konkatenieren, also zusammenhängen von Texten und Feldinhalten.

Das häufigste Beispiel dafür ist das Zusammenstellen einer Adresse aus einzelnen Adressfeldern. Das übliche Vorgehen beim Kombinieren von Texten sieht so aus:

[Titel] & " " & [Vorname] & " " & [Nachname]

Die drei Felder werden also mit Hilfe des kaufmännischen Und (auch “Ampersand” genannt) verbunden und dazwischen werden Leerzeichen eingefügt.

Es kommt aber vor, dass Daten unvollständig sind, weil zum Beispiel ein Vorname fehlt oder jemand keinen Titel hat. Wenn nun eines der vorderen Felder leer ist, wird das folgende Leerzeichen trotzdem eingefügt und stört, beispielsweise in einem Bericht, in dem die ganze Adresse linksbündig ausgerichtet sein soll.

VBA besitzt die Trim-Funktion, um führende und nachgestellte Leerzeichen zu entfernen.

Trim([Titel] & " " & [Vorname] & " " & [Nachname])

Wenn im Beispiel [Titel] oder [Nachname] keinen Wert enthalten, entfernt Trim das Leerzeichen. Wenn aber [Vorname] keinen Wert hat, stehen zwischen dem Titel und dem Nachnamen trotz des Trim zwei Leerzeichen.

Eine bessere Lösung sieht daher folgendermaßen aus:

[Titel] + " " & [Vorname] + " " & [Nachname]

Oder zwecks Deutlichkeit mit Klammern geschrieben:

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

Schreibe einen Kommentar

Access-FAQ

Karl Donaubauer, Wien

In der Access-FAQ von Karl Donaubauer (www.donkarl.com) finden Sie die meistgestellten Fragen und Antworten 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 dritten Teil lernen Sie die Lösungen zu den meistgenannten Problemen der Teilnehmer der deutschsprachigen Access-Newsgroups bei der Behandlung von Zahlen und Zeiten kennen.

Obwohl Access-Anwendungen sicher überwiegend zum Organisieren von Zahlen und besonders Geldwerten eingesetzt werden, ist Access – ganz im Gegensatz zu Excel – erstaunlich schlecht mit den dafür nötigen Rundungsfunktionen ausgerüstet. Genau gesagt gibt es nur eine einzige eingebaute Rundungsfunktion und die ist leider für die Praxis untauglich.

Die häufigste Rundungsproblematik ist das korrekte kaufmännische Runden, also gleich bleibender Wert der zu rundenden Stelle, wenn eine Zahl zwischen 0 und 4 folgt, und Aufrundung der zu rundenden Stelle, wenn eine Zahl zwischen 5 und 9 folgt. In der Praxis geht es meistens darum, einen Geldbetrag auf zwei Kommastellen genau kaufmännisch zu runden.

Die meisten Fehler passieren, weil dafür ungeeignete Zahlentypen, Funktionen oder Ad-hoc-Formeln verwendet werden. Die tiefere Ursache sind meist Ungenauigkeiten durch die interne Fließkommabehandlung von Dezimalzahlen. Zahlen vom Typ Währung, den man für Geldwerte immer verwenden sollte, sind gegen diese Ungenauigkeiten etwas resistenter als zum Beispiel solche vom Typ Double. Access gibt allerdings bei Berechnungen in Abfragen, Formularen, Berichten – auch bei Zahlen vom Typ Währung – wiederum Double-Werte zurück.

Deshalb sollte eine zuverlässige Rundungsfunktion mit Double-Werten umgehen können.

Hinweis

Auf der Begleit-CD finden Sie eine Beispiel-Datenbank für Access 97 (FAQ97.mdb) und Access 2000 und höher (FAQ00.mdb) mit den Quellcodes und Beispielen aus diesem Artikel.

Int() und Fix()

Bei einfacher Verwendung dieser beiden Funktionen nach dem Muster

Int(Zahl * 100 + 0,5)/100

stimmen zwar viele Ergebnisse, aber nicht alle. Beispiele:

Int(5,025 * 100 + 0,5)/100

ergibt das erwartete Ergebnis 5,03.

Int(1,025 * 100 + 0,5)/100

ergibt bei Double-Werten 1,02 und bei Währungswerten korrekt 1,03.

Bei einer negativen Währungszahl scheitert die Berechnung wieder:

Int(-1,025 * 100 + 0,5)/100

ergibt nicht korrekt -1,02.

CLng() und CInt()

Diese beiden Funktionen runden nicht kaufmännisch, sondern bei einer fünf hinter der Stelle, auf die gerundet werden soll, auf die nächste gerade Zahl.

CLng(0.5) ergibt beispielsweise 0, während CLng(1.5) als Ergebnis den Wert 2 ausgibt. Darauf wird zwar in der Online-Hilfe zu diesen Funktionen hingewiesen, wird aber oft übersehen.

Round()

Diese ab Access 2000 in VBA eingebaute (einzige) Rundungsfunktion rundet nach dem gleichen Prinzip wie CDbl und CInt, also bei einer fünf hinter der Stelle, auf die gerundet werden soll, auf die nächste gerade Zahl.

Zum Beispiel ergibt Round(1,085; 2) korrekterweise den Wert 1,08, weil 8 die nächste gerade Zahl ist.

Microsoft bezeichnet diese Art der Rundung als “Banker”s Rounding”. Leider ist sie für alle mir bekannten Anwendungen unbrauchbar und der Grund für ihren Einbau seit jeher rätselhaft.

Hinzu kommt, dass Round() fehlerhaft arbeitet, wenn man voraussetzt, dass es mit Double-Werten zurechtkommen sollte:

So ergibt zum Beispiel Round(1,035;2) den Wert 1,03 statt des korrekten Wertes 1,04.

Resumée: Finger weg von dieser Funktion!

Weitere Informationen zu Round() und zur Rundung nach Microsoft-Vorstellungen finden Sie in den Knowledgebase-Artikeln 225330 und 196652.

Format

Die Funktion Format() ist differenzierter zu sehen. In Access 97 hat sie ähnliche Probleme mit Fließkommawerten wie Int() und Fix(), wenn man sie etwa in dieser Weise einsetzt:

Format(Zahl;"#,##")

Interessant ist, dass diese Probleme offenbar ab Access 2000 behoben wurden. In den neueren Access-Versionen sind mir bisher noch keine Fehler bei der kaufmännischen Rundung mit der Format-Funktion begegnet.

Die Formulierung ist deshalb so vorsichtig, weil Format() weder als Rundungsfunktion definiert ist, noch die änderungen nach Access 97 dokumentiert sind.

Microsoft hat Access also nie eine definierte und funktionierende Methode zur kaufmännischen Rundung spendiert. Deshalb kursieren unzählige Varianten von benutzerdefinierten Funktionen. Viele davon runden falsch, weil sie sich auf die oben angeführten unzureichenden Methoden stützen.

Wenn Sie bereits eine benutzerdefinierte Funktion zum kaufmännischen Runden verwenden, dann testen Sie diese mit den hier verwendeten und oft problematischen Zahlen (insbesondere 1.025, -1.025). Sollten sie diese Tests nicht bestehen, bietet Quellcode 1 eine Rundungsfunktion, die seit Jahren vielfach erprobt ist.

Wenn Sie diese Funktion in einem Standardmodul speichern, kann sie in der ganzen Datenbank zur kaufmännischen Rundung verwendet werden. Dem Argument varNr wird die zu rundende Zahl übergeben, dem Argument varPl die Anzahl der Dezimalstellen. Dieses zweite Argument ist optional und bereits mit dem Standardwert 2 versehen. Wird beim Aufruf der Funktion das Argument nicht übergeben, rundet die Funktion automatisch auf zwei Dezimalstellen. Der folgende Aufruf ergibt daher den kaufmännisch gerundeten Wert 1.03:

fctRound(1.025)
Function fctRound(Optional varNr, Optional varPl As Integer = 2) As Double
    If IsMissing(varNr) Or Not IsNumeric(varNr) Then Exit Function
    fctRound = Fix("" & varNr * (10 ^ varPl) + Sgn(varNr) * 0.5) / (10 ^ varPl)
End Function

Quellcode 1

Public Function fctExcelRound(argNumer, Optional argPlaces = 2)
    fctExcelRound = Excel.Application.Round(argNumer, argPlaces)
End Function

Quellcode 2

Bild 1: Rundungsvarianten in der Beispiel-mdb

Andere Beispiele für Aufruf und Ergebnis:

fctRound(1.25, 1) ergibt 1.3,

fctRound(1250, -2) ergibt 1300.

Bild 1 zeigt gut und schlecht funktionierende Rundungsvarianten im Formular frmKaufRunden der Beispieldatenbank.

Access bietet indirekt noch eine andere Möglichkeit zur kaufmännischen Rundung, indem man Excel als Rundungstool einsetzt. Das Vorgehen ist recht einfach. öffnen Sie ein beliebiges VBA-Modul, wählen Sie den Menüpunkt Extras ( Verweise, setzen Sie einen Verweis auf die Excel-Objektbibliothek. In Office XP heißt die Bibliothek zum Beispiel Microsoft Excel 10.0 Object Library (siehe Bild 2).

Ab sofort stehen in der Datenbank sämtliche Rundungsfunktionen von Excel zur Verfügung. Excel besitzt für die kaufmännische Rundung die Tabellenfunktion Round().

Diese ist nicht identisch mit der oben erwähnten, gleichnamigen VBA-Funktion, sondern rundet zuverlässig kaufmännisch.

Am besten kapselt man die Excel-Tabellenfunktion mit einer eigenen Funktion in einem Standardmodul (Quellcode 2).

Aufruf und Handhabung erfolgen analog zu der oben angeführten benutzerdefinierten Funktion. Als Beispiel ein Steuerelementinhalt:

=fctExcelRound(1,025;2)

Voraussetzung für die Nutzung der Excel-Bibliothek ist natürlich, dass Excel auf dem Zielsystem vorhanden ist.

Bild 2: Verweis auf die Objektbibliothek von Excel

Für das Drucken von Zahlscheinen, Schecks und so weiter ist es notwendig, den Geldbetrag als geschriebenes Wort anzugeben. Beispiel: 4321 wird zu viertausenddreihunderteinundzwanzig. Quellcode 3 und Quellcode 4 enthalten zwei Funktionen, die zusammen diese Arbeit erledigen. Kopieren Sie den Code in ein Standardmodul. Der Aufruf ist schlicht:

FctZahl_In_Worten(4321)

Für den richtigen Umgang mit Datum und Uhrzeit in Access muss man wissen, wie Access diese Art von Daten intern handhabt.

Der Felddatentyp Datum/Uhrzeit funktioniert im Bereich 1.1.100 bis 31.12.9999. In einem Feld dieses Typs befinden sich immer Datum und Uhrzeit, auch wenn durch die Formatierung des Feldes nur einer dieser Teile zu sehen sein sollte. Intern speichert Access Datum/Uhrzeit als Kommazahl. Das Datum steht vor dem Komma, die Uhrzeit nach dem Komma. Der Zeitpunkt 0, ab dem gezählt wird, ist in Access der 30.12.1899 00:00 (in Excel ist es der 1.1.1900). Der Tag 1 ist der 31.12.1899, Tag 2 der 1.1.1900 und so weiter. ältere Datumswerte werden als negative Zahl gespeichert (-1 bedeutet zum Beispiel 29.12.1899 00:00).

Die Uhrzeit wird in Form des Bruchteils eines Tages nach dem Komma gespeichert. 0,5 ist daher der 30.12.1899 12:00. Mithilfe der Umwandlungsfunktionen CDbl() und CDate kann man von Datum auf Zahl und von Zahl auf Datum umrechnen. Ein Beispiel aus dem Testfenster des VBA-Editors:

 CDbl(#1/1/2004 18:00#)
37987,75

Die Zahl vor dem Komma zeigt, dass der 1.1.2004 der 37987. Tag seit dem 30.12.1899 ist. Die Zahl nach dem Komma zeigt, dass 18:00 Uhr drei Viertel beziehungsweise 75% eines Tages sind. Das funktioniert auch umgekehrt:

CDate(37987.75)
01.01.2004 18:00:00

Diese Art der Speicherung ermöglicht für positive Zahlen, also den Bereich seit dem 30.12.1899, das arithmetische Rechnen mit Datum und Uhrzeit.

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

Schreibe einen Kommentar

Access-FAQ

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 zweiten Teil lernen Sie die Lösungen zu den häufigsten Problemen der Teilnehmer der deutschsprachigen Access-Newsgroups zu verschiedenen Themen rund um die Handhabung der Access-Datei als Ganzes kennen.

Access bietet vielfältige Möglichkeiten, beim Starten einer Datenbank Einstellungen vorzunehmen. Das war das Hauptthema des ersten Teils dieser Artikelserie. Oft möchte man aber auch beim Schließen einer Datenbank Aktionen durchführen beziehungsweise das Schließen verhindern, solange nicht eine bestimmte Aktion durchgeführt wurde. Leider sieht Access kein Ereignis vor, das beim Schließen der Datenbank ausgelöst wird. Weder gibt es ein Autoexec-Makro noch Options-Einstellungen wie für das Starten. Daher behilft man sich eines Workarounds.

Man kann und sollte dem Benutzer eine Möglichkeit zum kontrollierten Beenden der Datenbankanwendung geben, wie zum Beispiel eine selbst programmierte Beenden-Schaltfläche. Damit hat man aber leider keine Garantie, dass der Anwender diesen Weg auch geht, denn dank Windows gibt es verschiedene Varianten, um Fenster zu schließen und Anwendungen zu beenden. Er kann das Schließen-Symbol oder den Schließen-Menüpunkt in der Titelleiste des Datenbank- oder Access-Fensters ebenso verwenden wie die Windows-Tastenkombinationen Alt + F4 (schließen der aktuellen Anwendung) und Strg + F4 (schließen des aktuellen Fensters) oder gar den Windows-Taskmanager.

Der Workaround, mit dem man den Moment des Schließens/Beendens sicher erwischt, sieht folgendermaßen aus: Man öffnet beim Start der Datenbank ein unsichtbares Formular. Dieses Formular bleibt im Hintergrund ständig offen, während die Datenbank benutzt wird. Wenn der Anwender irgendwann die Datenbank schließt, treten unweigerlich die Ereignisse Beim Entladen und Beim Schließen des versteckten Formulars ein. Für diese beiden Ereignisse können Sie Code hinterlegen, der beim Schließen der Datenbank ausgeführt werden soll.

Wenn Sie beispielsweise sicherstellen möchten, dass der Benutzer die Datenbank nicht schließen kann, ohne noch eine bestimmte Aktion oder Eingabe zu machen, erstellen Sie zunächst ein Formular namens frmKontrolle. Legen Sie als einziges Steuerelement des Formulars ein Kontrollkästchen chkKontrolle mit dem Standardwert Falsch an. Hinterlegen Sie die folgende Prozedur für die Ereigniseigenschaft Beim Entladen des Formulars:

If Me!chkKontrolle = False Then _    Cancel = True
''öffnen einer weitere Datenbank mit Fokus
Shell "C:\Pfad\MSAccess.exe C:\Pfad\Weitere.mdb", _    vbNormalFocus 
DoCmd.Quit ''Beenden der aktuellen Instanz von Access

Quellcode 1

Bild 1: Das untere Formular frmKontrolle ist hier nur ausnahmsweise sichtbar geschaltet.

Dieses Abbrechen des Entladens verhindert das Schließen der Datenbank und damit das Beenden von Access.

Wenn die Bedingung zum Schließen der Datenbank erfüllt ist, setzen Sie das Kontrollkästchen auf Wahr:

Forms!frmKontrolle!chkKontrolle = True

Damit kann das unsichtbare Formular entladen und die Datenbank beziehungsweise Access geschlossen werden. Das Kontrollformular wird durch das Ereignis Beim öffnen des Startformulars per VBA geöffnet. Das unsichtbare öffnen besorgt der letzte Parameter in folgender Code-Zeile:

DoCmd.OpenForm "frmKontrolle", , , , , acHidden

Eine andere Variante ist das öffnen im Autoexec-Makro mit der öffnenFormular-Aktion und der Einstellung Fenstermodus: Ausgeblendet.

Hinweis

Auf der Begleit-CD finden Sie eine kleine Beispiel-Datenbank für Access 97 (FAQ97.mdb) und Access 2000 und höher (FAQ00.mdb) mit den soeben beschriebenen Beispielen.

Das Startformular frmEingabe verlangt eine bestimmte Eingabe durch den Anwender, damit er die Datenbank wieder schließen kann(siehe Bild 1).

Dieses Vorgehen bietet keine hundertprozentige Sicherheit. Fälle, die es nicht abdeckt, sind Abstürze von Access oder von Windows, Netzwerkunterbrechungen, Netzkabelziehen und ähnliches. Daran sollten Sie denken, wenn Sie wichtige Vorgänge mit dieser Methode steuern.

Ein häufiger Anwendungsfall ist zum Beispiel eine selbst erstellte User-Verwaltung, bei der der Anwender mit dem Schließen des versteckten Formulares ausgeloggt wird. Bei einem Absturz findet das nicht statt. Darauf muss die selbst erstellte User-Verwaltung vorbereitet sein.

Access kann gleichzeitig immer nur eine Datenbank pro Hauptfenster sichtbar geöffnet haben. öffnet man eine weitere Datenbank, beispielsweise durch einen Doppelklick im Windows-Explorer, so wird dafür eine neue, zusätzliche Instanz von Access geöffnet.

Das ist bei modernen Computern mit viel Arbeitsspeicher zum Glück kein solches Problem mehr wie noch vor ein paar Jahren.

Mit dem Beispiele-Code aus Quellcode 1 öffnet man eine weitere Datenbank in einer neuen Instanz von Access und schließt die aktuelle.

Sub procDelObj1()
    On Error GoTo Error_procDelObj
    '' andere Datenbank per DAO öffnen
    Dim db As DAO.Database
    Set db = DBEngine.Workspaces(0). _        OpenDatabase("c:\Pfad\DieAndere.mdb")
    '' Tabelle oder Abfrage löschen
    ''db.TableDefs.Delete "MeineTabelle"
    '' oder db.QueryDefs.Delete "MeineAbfrage"
Exit_procDelObj:
    '' Variablen expl. freigeben und Prozedur verlassen
    db.Close: Set db = Nothing
    Exit Sub
Error_procDelObj:
    '' Fehlermeldung ausgeben und mit Aussprungmarke
    '' fortsetzen
    Msgbox "Fehler Nr. " & Str(err.Number) & " " _        & err.Description
    Resume Exit_procDelObj
End Sub

Quellcode 2

Sub procDelObj2()
    On Error GoTo Error_procDelObj
    '' neue Access-Instanz öffnen
    Dim appAcc As Access.Application
    Set appAcc = New Access.Application
    '' andere DB öffnen und Objekt löschen
    appAcc.OpenCurrentDatabase "c:\...\DieAndere.mdb"
     appAcc.DoCmd.DeleteObject acReport, "MeinBericht"
    '' oder appAcc.DoCmd.DeleteObject acform, _        "MeinFormular"
Exit_procDelObj:
    '' Access-Instanz schließen, Speicher freigeben und     '' Prozedur verlassen
    appAcc.Quit
    Set appAcc = Nothing
    Exit Sub
Error_procDelObj:
    '' Fehlermeldung ausgeben
    Msgbox "Fehler Nr. " & Str(err.Number) & " " _        & err.Description
    Resume Exit_procDelObj
End Sub

Quellcode 3

Komplizierter wird es, wenn man die zweite Access-Instanz vermeiden möchte. Der Menüpunkt Datei/Datenbank öffnen kann das zwar ohne Problem, aber Microsoft hat keine programmatische Variante für diese Aktion vorgesehen.

Wenn also der Menüpunkt in einer Anwendung nicht zur Verfügung steht oder nicht benutzt werden soll, bleiben nur zwei Varianten. Die eine ist das Nachbilden der Tastaturkombination zum Aktivieren des Menüpunktes mit Hilfe der Sendkeys-Funktion. Diese Variante ist wegen der Problematik von Sendkeys nicht empfehlenswert. Besser, wenn auch etwas aufwändiger, ist die Verwendung des kostenlosen Add-Ins TSI SOON von Michael Kaplans Webseite www.trigeminal.com/utility.asp. Das Add-In enthält Installationsanweisungen und bietet noch weitere Features.

Wie löscht man Datenbankobjekte, die sich nicht in der aktuellen Datenbank, sondern in einer anderen befinden Bei der Lösung kommt es auf die Art des Objektes an. Für Tabellen und Abfragen ist der (Lösch-)Zugriff ohne viel Aufwand mit DAO (oder ADO) möglich, wie das Beispiel aus Quellcode 2 zeigt.

Komplizierter ist es für andere Objekte wie Formulare und Berichte. Dazu benötigt man etwas Automatisierungs-Code, der die andere Datenbank kurz in einer weiteren Access-Instanz öffnet, das Objekt löscht und die Access-Instanz wieder schließt. Ein Code-Beispiel finden Sie in Quellcode 3.

Bild 2: Import-Dialogfeld mit erweiterten Optionen

Diesen Automatisierungscode können Sie nicht nur zum Löschen, sondern mit leichten Anpassungen auch für viele weitere “Fernsteuerungen” anderer Datenbanken verwenden. In den meisten Fällen genügt es, folgende Zeile anzupassen:

appAcc.DoCmd.DeleteObject acReport, _    "MeinBericht"

Sie können hier statt dessen zum Beispiel jeden DoCmd-Befehl verwenden, den Sie auch in der aktuellen Datenbank benutzen. Häufige Anwendungsfälle, weil kaum anderweitig zu lösen, sind folgende:

  • Objekte innerhalb der anderen Datenbank kopieren (appAcc.DoCmd.CopyObject)
  • Objekte in der anderen Datenbank umbenennen (appAcc.DoCmd.Rename)
  • Im-/Exporte von/zu dritten Datenbanken durchführen (appAcc.DoCmd.TransferDatabase)
  • All diese DoCmd-Methoden besitzen natürlich weitere Parameter, die Sie angeben müssen. Sie sind in der Online-Hilfe beschrieben. Es sei aber darauf hingewiesen, dass Sie per Automatisierung aus der aktuellen Datenbank heraus Aktionen in einer anderen Datenbank fast beliebig steuern können.

    Dennoch sollten Sie Automation nur mit Bedacht einsetzen, nämlich wenn es für Ihr Vorhaben keine anderen Methoden mit weniger Speicherbedarf, Zeitaufwand und Komplexität (für Windows) gibt.

    Wie übernimmt man selbst definierte Symbolleisten und Import-/Exportspezifikationen in eine andere Datenbank

    Für das Exportieren gibt es an der Access-Oberfläche keine Möglichkeit. Nur beim Importieren in eine andere Datenbank bietet das Import-Dialogfenster Optionen zum übernehmen. Seltsamerweise sind diese wichtigen Optionen etwas versteckt.

    In der Datenbank, in die man die Menüs, Symbolleisten oder Spezifikationen übernehmen möchte, wählt man den Menüpunkt Datei/Externe Daten/Importieren und dort die entsprechende Quelldatenbank. Im Import-Dialogfenster zur Auswahl der Objekte klickt man auf die Schaltfläche Optionen>>, um das Dialogfenster zu erweitern. Dort kann man nun anhaken, dass die entsprechenden Einstellungen mit importiert werden (siehe Bild 2). Es ist übrigens nicht notwenig, auch “normale” Datenbankobjekte für den Import auszuwählen, wenn man nur diese erweiterten Möglichkeiten nutzen möchte. Ein Klick auf OK importiert sämtliche Menü- und Symbolleisten beziehungsweise Import- und Exportspezifikationen. Eine detaillierte Auswahl ist hier nicht möglich.

    In Versionen bis Access 97 gibt es eine Systemtabelle namens MSysCmdbars, in der die Daten für selbst definierte Menü- und Symbolleisten gespeichert sind. Wenn man die Systemobjekte im Menü Extras/Optionen/Ansicht auf sichtbar stellt, kann man diese Tabelle nutzen, um die Einstellungen im Ganzen oder auch zeilenweise, das bedeutet jede Symbolleiste extra, zu exportieren oder zu importieren. In höheren Versionen von Access werden die Einstellungen für die Commandbars jedoch anders gespeichert. Daher funktioniert dort diese althergebrachte Methode nicht mehr.

    Etwas anders sieht es bei den Import- und Exportspezifikationen aus. Sie sind auch in den neueren Versionen nach wie vor in den beiden Systemtabellen MSysIMEXSpecs und MSysIMEXColumns gespeichert.

    Falls aus irgendwelchen Gründen das manuelle übernehmen mittels Import-Dialogfeld nicht möglich oder nicht erwünscht ist, kann man auf diese beiden Tabellen per SQL oder VBA zugreifen.

    Bild 3: Die Eigenschaft Ausgeblendet der Tabelle ist aktiviert.

    Bild 4: Die Tabelle tbl_Kunden ist am helleren Symbol als ausgeblendet zu erkennen.

    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

    Schreibe einen Kommentar