Home > Artikel > Ausgabe 10/2015 > Recordset-Typen

Recordset-Typen

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Wenn Sie lesend oder schreibend auf die Daten von Tabellen und Abfragen unter VBA zugreifen, so steht fast immer ein DAO-Recordset im Mittelpunkt des Geschehens. Dass es von diesem unterschiedliche Varianten gibt, wird dabei gern übersehen. Untersuchen wir hier, welche Typen existieren und wie sie sich in ihrem Verhalten unterscheiden.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1510_DAO_Recordsets.accdb

Öffnen eines Recordsets

Der Zugriff auf die Daten einer Tabelle oder Abfrage geschieht unter DAO und VBA immer über ein Recordset, welches auf ein Database-Objekt und seine Methode OpenRecordset geöffnet wird. Access stellt eine Instanz der aktuellen Datenbank über seine Funktion CurrentDb bereit. Über sie greifen Sie etwa auf eine Tabelle tblAdressen zu, indem Sie deren Namen der OpenRecordset-Funktion als Parameter übergeben. Im einfachsten Fall sieht das so aus:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset_

("tblAdressen")

Statt direkt eine Tabelle anzugeben, kann auch ein SQL-String eingesetzt werden:

Set rs = CurrentDb.OpenRecordset_

("SELECT * FROM tblAdressen")

Das Ergebnis ist dasselbe. Ist der SQL-String sehr komplex und die Schreibarbeit unter VBA damit zu aufwändig, so kommt auch das Öffnen eines Recordset über eine Abfrage in Betracht:

Dim qdf As QueryDef

Dim rs As DAO.Recordset

Set qdf = CurrentDb.QueryDefs_

                  ("qryAdressen")

Set rs = qdf.OpenRecordset()

Hier befände sich der SQL-String in der Auswahlabfrage qryAdressen. Das Ergebnis ist auch hier identisch mit dem Ergebnis des über einen SQL-String direkt geöffneten Recordset-Objekts, wie zuvor.

Was bei der ganzen Sache unterschlagen wird, ist der zweite Parameter, der der Methode OpenRecordset übergeben werden kann. Er gibt an, welcher Recordset-Typ von der Methode zurückgegeben werden soll. Dies könnte etwa der Typ Dynaset sein, welcher in der Folge das Bearbeiten der Daten erlaubte:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset_

("tblAdressen", dbOpenDynaset)

Was jedoch passiert, wenn Sie diesen Parameter weglassen? Welcher Recordset-Typ kommt dabei heraus?

Das können Sie ermitteln, indem Sie anschließend die Type-Eigenschaft abfragen:

Debug.Print rs.Type

Sie erhalten damit eine Zahl, die einen der Elemente von RecordsetTypeEnum wiederspiegelt. Öffnen Sie den VBA-Objektkatalog, stellen das obere Kombinationsfeld auf DAO ein und navigieren in der Klassenliste zu dieser Enumeration.

Rechts im Methodenfenster finden Sie nun die fünf möglichen Typen. Beim Klick auf einen der Einträge sehen Sie im Beschreibungsfenster ganz unten den Wert der Konstanten. Für unser erstes Beispiel wird der Wert 1 und damit der Typ dbOpenTable ermittelt.

Doch die Annahme, dass bei Weglassen des optionalen Typ-Parameters für OpenRecordset das Ergebnis-Recordset immer vom Typ Table ist, trügt leider. Denn für das zweite Beispiel mit dem SQL-String bekommen Sie den Typ-Wert 2, was dem Dynaset entspricht. Gleiches gilt für das auf die Abfrage geöffnete Recordset. Und auch bei verknüpften Tabellen, die sich in einer anderen Datenbank befinden, stellt DAO den Typ automatisch auf Dynaset ein.

Aus diesem Grund ist es ratsam, den Typ-Parameter grundsätzlich anzugeben. Denn das Verhalten der Recordset-Varianten unterscheidet sich durchaus, wie wir gleich sehen werden.

Recordset-Typ Table

Dieser über den Parameterwert dbOpenTable erhaltene Typ lässt sich ausschließlich für die in der aktuellen Datenbank befindlichen Tabellen verwenden. Verknüpfte Tabellen und Abfragen kommen hier nicht infrage. Andernfalls käme es beim Aufruf zu einer Fehlermeldung. Seine Datensätze sind aktualisierbar, was bedeutet, dass Datenwerte geändert und -zeilen gelöscht oder hinzugefügt werden können.

Da auch der Typ Dynaset dies unterstützt, fragt sich, wofür dieser Typ überhaupt gut ist. Es gibt genau einen Grund: Nur dieser Typ erlaubt den Aufruf der Methode Seek zum schnellen Suchen von Datensätzen auf Felder, die indiziert sind. Dazu später mehr.

Recordset-Typ Dynaset

Mit dem Parameter dbOpenDynaset wird von OpenRecordset dieser Typ zurückgegeben. Auch hier sind die Datensätze bearbeitbar. Allerdings nicht nur für in der aktuellen Datenbank befindliche Tabellen, sondern auch für verknüpfte Tabellen und Abfragen. Bei Letzteren ist allerdings Voraussetzung, dass sie selbst einen aktualisierbaren Aufbau besitzen. Öffnen Sie die fragliche Abfrage einfach als Datenblatt und versuchen Sie darin Datensätze zu bearbeiten. Klappt das, so kann auf die Abfrage mit diesem Recordset-Typ zugegegriffen werden. Doch auch nichtaktualisierbare Abfragen verweigern diesen Parameter dbOpenDynaset nicht. Allerdings wird in der Folge beim Editieren von Datensätzen eine Fehlermeldung ausgegeben.

Recordset-Typ Snapshot

Geben Sie dbOpenSnapshot als Typ-Parameter für das Recordset an, so einsteht eine Datenquelle, deren Datensätze nicht bearbeitet werden können. Sie können so sicherstellen, dass nicht aus Versehen Daten von Tabellen verändert werden. Das jedoch ist auch schon der einzige sinnvolle Grund, diesen Typ einzusetzen. Denn die Performance dieses Typs ist, wie wir noch sehen werden, recht mangelhaft

Unter der Haube geschieht technisch nämlich dies: Bei einem Snapshot werden alle Datensätze der Tabelle oder Abfrage komplett in den lokalen Speicher der Daten-Engine eingelesen, während bei einem Dynaset zunächst nur ein Bruchteil ermittelt wird und die weiteren Datensätze erst auf Anfrage eintrudeln. Diese Anfragen entstehen erst durch die Move- und Find-Methoden des Recordsets. Damit wird auch klar, weshalb die Funktion RecordCount des Recordsets, welche die Zahl der Datensätze zurückgibt, bei beiden Typen unterschiedliche Ergebnisse zeigt. Beim Snapshot ist der Wert korrekt, da bereits alle Datensätze im Speicher liegen. Beim Dynaset jedoch erhalten Sie zunächst immer die 1, weil noch kein weiterer Zugriff auf die Datensätze stattfand. Erst nach einem Aufruf von MoveLast stimmt die Zahl.

Besonders katastrophal verhält sich das Snapshot beim Zugriff auf große verknüpfte Tabellen, die in einer Backend-Datenbank auf einem Netzwerk-Share liegen. Da hier die gesamte Tabelle oder das gesamte Abfrageergebnis übertragen werden muss, kann es zu deutlichen Verzögerungen und Performance-Einbrüchen kommen.

Lange Rede, kurzer Sinn: Selten gibt es einen vernünftigen Grund, diesen Typ einzusetzen.

Recordset-Typ ForwardOnly

Das über den Typ-Parameter dbOpenForwardOnly erhaltene Recordset weist ähnliche Einschränkungen auf, wie ein Snapshot. Die Datenquelle ist nicht aktualisierbar. Zu allem Überfluss lassen sich darauf noch nicht einmal die Find-Methoden (FindFirst, FindLast, et cetera) anwenden. Allerdings liegt es in der Performance in der Region eines Dynaset, weil hier ebenfalls zunächst nur der erste Datensatz vorliegt und nicht alle Datensätze auf einen Schwung übertragen werden müssen. Man kann sich innerhalb der Datensätze nur in eine Richtung bewegen: vorwärts. Das bedeutet, dass lediglich die Methode MoveNext zur Navigation erlaubt ist, während MovePrevious und sogar MoveLast fehlschlagen.

Mit all diesen Einschränkungen empfiehlt sich der Einsatz dieses Typs überhaupt nicht. Theoretisch könnte die Performance bei Durchlaufen der Datensätze hier die beste sein, das jedoch bestätigt sich unter keinen Umständen.

Der Übersicht halber finden Sie in Bild 1 eine Matrix, die die einzelnen Recordset-Typen und deren erlaubte Methoden auflistet. Read bedeutet dabei, dass Werte aus den Datensätzen ausgelesen werden können. Das ist selbstverständlich bei allen Typen der Fall. Beim Modifizieren von Datensätze (Edit- und Update-Methoden) sieht das schon anders aus. Die Find-Methoden FindFirst, FindNext, FindPrevious und FindLast spendieren nur Dynasets und Snapshots. Die recht spezielle Seek-Methode schließlich erlaubt nur das Table-Recordset.

Recordset-Typen und ihre erlaubte Zugriffsmethoden

Bild 1: Recordset-Typen und ihre erlaubte Zugriffsmethoden

Aufsuchen von Datensätzen mit Seek

Während das Suchen innerhalb der Datensätze eines Recordsets über dessen Find-Methoden bereits in einigen Ausgaben von Access Basics behandelt wurde, soll die Seek-Methode hier etwas ausführlicher vorgestellt werden.

Während die Find-Methoden den Inhalt der Datensätze nacheinander Schritt für Schritt mit dem Suchausdruck vergleichen, bis Übereinstimmung festgestellt wird, geht Seek einen anderen Weg. Hier wird direkt der Index eines Feldes herangezogen, über den sehr schnell ein Datensatz identifiziert ist.

Damit sind schon drei Voraussetzungen gegeben:

  • Das Recordset muss über den Parameter dbOpenTable geöffnet worden sein.
  • Das Feld, auf welches die Suche sich bezieht, muss in der Tabelle einen Index aufweisen. Er muss nicht unbedingt eindeutig sein.
  • Es kann über Seek nur ein einziges Feld abgefragt werden, da ja auch nur ein Index angesprochen wird. Die Suche nach mehreren Kriterien entfällt hier also.

Diese Voraussetzungen sind in der Tabelle tblAdressen der Beispieldatenbank etwa für das Feld Nachname erfüllt. Um den Datensatz mit einem bestimmten Namen per Seek aufzufinden, gehen Sie so vor:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset_

         ("tblAdressen", dbOpenTable)

rs.Index = "Nachname"

rs.Seek "=", "Schmidt"

If Not rs.NoMatch Then

     Debug.Print rs!ID, rs!Vorname

End If

Nach dem Öffnen des Recordset vom Typ Table wird der Eigenschaft Index der Name des abzufragenden Index übergeben. Achtung! Es handelt sich nicht um den Namen des Felds! Im Beispiel ist der Index-Name zwar gleichlautend, kann in anderen Fällen aber abweichen. Den Index-Namen können Sie im Tabellenentwurf ja einstellen, wenn Sie dort den Indizes-Dialog öffnen.

Für die Seek-Methode geben Sie mindestens zwei Parameter an. Der erste ist ein String mit dem Vergleichsmodus – hier ein Gleichheitszeichen. Als zweiten Parameter geben Sie den Vergleichsausdruck an – hier Schmidt. Wichtig dabei: der Datentyp dieses Ausdrucks muss gleich sein, wie das abgefragte Feld! Bei Nachname haben wir es mit Text zu tun, und daher ist der Vergleichsausdruck ein String. Würden Sie in einem Datumsfeld suchen, so müsste der Vergleichsausdruck den Datentyp Date aufweisen.

Außerdem ist es möglich mehr, als einen Vergleichsausdruck, anzugeben:

rs.Seek "=", "Schmidt", "Müller"

Bis zu 13 solcher Ausdrücke nimmt Seek entgegen.

Ob ein entsprechender Datensatz gefunden wurde, offenbart die Eigenschaft NoMatch des Recordsets, ähnlich, wie bei den Find-Methoden. Steht sie auf True, dann wurde kein Datensatz gefunden.Bei False hingegen schon. Die Routine gibt diesen Datensatz im Beispiel dann per Debug.Print im VBA-Direktfenster aus.

Neben dem Gleichheitszeichen als Vergleichsoperator können Sie auch andere einsetzen, wie >=, <=, >, <. Konsultieren Sie die Hilfe zu DAO, um mehr über deren Bedeutung zu erfahren.

Nach Drücken von F1 nachdem Sie die Seek-Methode im Objektkatalog oder im VBA-Code markiert haben, sollten sich die weiteren Erläuterungen zur Anweisung öffnen.

Nachteilig an der außerordentlich schnellen Seek-Methode ist, dass sie sich nur für die in der aktuellen Datenbank vorhandenen Tabellen verwenden lässt, nicht jedoch für verknüpfte Tabellen. Dennoch gibt es hierfür einen Workaround. Öffnen Sie nicht die verknüpfte Tabelle, sondern erzeugen Sie zunächst eine neue Database-Instanz auf die Backend-Datenbank. Auf diese können Sie dann ein Recordset vom Typ Table öffnen, wie in Listing 1.

Sub SeekBackendTable()

     Dim dbs As Database

     Dim rs As DAO.Recordset

     Dim sFile As String

     

     sFile = CurrentDb.TableDefs("tblAdressenBE").Connect

     sFile = Mid(sFile, 11)

     

     Set dbs = OpenDatabase(sFile)

     Set rs = dbs.OpenRecordset("tblAdressen", dbOpenTable)

     rs.Index = "Nachname"

     rs.Seek "=", "Schmidt"

     If Not rs.NoMatch Then Debug.Print rs!ID, rs!Vorname

     rs.Close

     dbs.Close

End Sub

Listing 1: Die Seek-Methode, indirekt auf eine verknüpfte Datenbank angewandt

Der Code nimmt zuerst auf die in die Datenbank verknüpfte Tabelle tblAdressenBE Bezug. Er erzeugt auf sie ein TableDef-Objekt und fragt in diesem die Connect-Eigenschaft ab. In der steht der vollständige Pfad zur verknüpften Datenbank. Allerdings etwa in dieser Form:

;DATABASE=C:\XYZ\backend.accdb

Die Mid-Funktion extrahiert den String ab dem elften Zeichen und speichert das Ergebnis in der Variablen sFile ab. Nun wird der Database-Variablen dbs eine neue Instanz des Backends über die Methode OpenDatabase zugewiesen. Sie braucht nur den Pfad der Backend-Datei, der ja in sFile zwischengespeichert ist. Der Rest des Codes fällt dann aus, wie für die Seek-Methode bereits beschrieben, nur, dass abschließend noch die Database-Instanz dbs geschlossen wird.

Sie sollten dieses Verfahren ernsthaft in Erwägung ziehen, wenn Sie es mit einer Mehrbenutzerumgebung und umfangreichen Tabellen zu tun haben, in denen Inhalte aufgesucht werden müssen. Die Seek-Methode ist um Größenordnungen schneller, als die Find-Methoden.

Was ist eine "umfangreiche Tabelle"? Nun, das hängt stark von den Umständen ab. Ist das Netzwerk schnell, so relativiert sich dieser Ausdruck. Da man es aber in der Regel immer noch mit 100-MBit-Kabeln und -Hubs zu tun hat, würden wir grob über den Daumen gepeilt diese Aussage wagen: Das Verfahren mit einer Backend-Instanz und Seek eignet sich immer für Tabellen mit mehr, als etwa 20.000 Datensätzen.

Performance der Recordset-Typen

Auch wenn mehrere Typen die gleichen Methoden zum Auslesen, Bearbeiten oder Suchen teilen, ist die Zugriffsgeschwindigkeit nicht immer dieselbe. Um das nachzuweisen, enthält die Beispieldatenbank im Modul mdlRSPerformance einige Prozeduren, die die einzelnen Typen einem Test unterziehen.

Zunächst geht es um das Auslesen der Datensätze. Hierfür nehmen Sie die Routine ReadTableRecordset in Listing 2. Ihr wird als Parameter der gewünschte Öffnungstyp in rstyp übergeben. Der Aufruf sähe also etwa so aus:

Function ReadTableRecordset(ByVal rstyp As _

                       DAO.RecordsetTypeEnum) As Single

     Dim dbs As Database

     Dim rs As DAO.Recordset2

     Dim fld As DAO.Field

     Dim V As Variant

     Dim t As Single

     Set dbs = CurrentDb

     t = VBA.Timer

     Set rs = dbs.OpenRecordset("tblAdressen", rstyp)

     Do

         For Each fld In rs.Fields

             V = Nz(fld.Value)

         Next fld

         rs.MoveNext

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!