Mit Arrays arbeiten

über Arrays unter VBA wurde schon vieles geschrieben und gesagt. Dennoch gibt es praktische Hinweise, die man gelegentlich benötigt, nicht so einfach im Internet. Also werfen wir in diesem Beitrag einen genaueren Blick auf die Möglichkeiten, die Arrays bieten, und liefern einige neue Funktionen rund um die Felder zum temporären Speichern und Bereitstellen von Daten. Unter anderem sehen wir uns an, wie Sie die Inhalte von Arrays zu Debugging-Zwecken im Direktbereich oder in einer Excel-Tabelle ausgeben oder wie Sie prüfen, ob ein Array leer ist.

Array aus Datensatz füllen

Manchmal möchten Sie vielleicht die Daten eines oder mehrerer Felder eines Datensatzes in einem Array speichern, um diese Daten performant bereitzustellen.

Wenn Sie nur ein Feld eintragen möchten, reicht ein eindimensionales Array aus. Dieses füllen Sie mithilfe der Funktion RecordsetInArrayEindimensional aus Listing 1. Die Funktion erwartet das einzulesende Recordset mit dem Parameter rst.

Public Function RecordsetInArrayEindimensional(rst As DAO.Recordset) As Variant()
     Dim arr() As Variant
     Do While Not rst.EOF
         ReDim Preserve arr(rst.AbsolutePosition)
         arr(rst.AbsolutePosition) = rst.Fields(0).Value
         rst.MoveNext
     Loop
     RecordsetInArrayEindimensional = arr
End Function

Listing 1: Einlesen eines Feldes eines Recordsets in ein eindimensionales Array

Es deklariert ein Array namens arr mit dem Datentyp Variant. In einer Do While-Schleife durchläuft die Funktion alle Datensätze des Recordsets und führt dabei zwei Anweisungen aus:

  • das ReDimensionieren des Arrays entsprechend der aktuellen Position des Datensatzzeigers (AbsolutePosition) sowie
  • das Zuweisen des Werts des ersten Felds des aktuellen Datensatzes an die entsprechende Position des Arrays.

Danach übergibt die Funktion das Array als Funktionswert an die aufrufende Routine. Diese sieht beispielsweise wie in Listing 2 aus. Die Routine füllt das Recordset-Objekt mit den Datensätzen der Tabelle tblArtikel, wobei nur das Feld Artikelname berücksichtigt wird.

Public Sub Test_RecordsetInArray()
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim i As Integer
     Dim arr() As Variant
     Set db = CurrentDb
     Set rst = db.OpenRecordset("SELECT Artikelname FROM tblArtikel", dbOpenDynaset)
     arr = RecordsetInArrayEindimensional(rst)
     For i = LBound(arr) To UBound(arr)
         Debug.Print arr(i)
     Next i
End Sub

Listing 2: Test zum Einlesen eines eindimensionalen Arrays aus einem Recordset

Dann ruft sie die Funktion Recordset-In-ArrayEindimensional auf und übergibt das Recordset als Parameter.

Um zu prüfen, ob alle Werte wie gewünscht in das Array eingelesen wurden, durchläuft die Routine anschließend in einer For…Next-Schleife alle Elemente des Arrays und gibt den jeweils aktuellen Wert im Direktbereich des VBA-Editors aus.

ReDim vorziehen

Wenn schon vorher klar ist, wie viele Elemente im Array landen sollen, können Sie dieses natürlich auch bereits vor dem Eintritt in die Do While-Schleife redimensionieren.

Der Versuch, dies direkt beim Deklarieren des Arrays zu erledigen, schlägt allerdings fehl – mit der Fehlermeldung Konstanter Ausdruck erforderlich, wie in Bild 1 zu erkennen.

Automatische Ergänzung eines Feldnamens

Bild 1: Automatische Ergänzung eines Feldnamens

Also deklarieren wir das Array in einer neuen Version der Funktion (s. Listing 3) zunächst ohne Angabe der Anzahl der enthaltenen Elemente und ändern diese dann später mit der ReDim-Anweisung. Dieser übergeben wir den Ausdruck rst.RecordCount. Damit diese Eigenschaft des Recordsets immer den richtigen Wert liefert, verschieben wir den Datensatzzeiger zuvor mit MoveLast einmal an das Ende der Datensatzgruppe und mit MoveFirst wieder an die erste Position.

Public Function RecordsetInArrayEindimensional(rst As DAO.Recordset) As Variant()
     Dim arr() As Variant
     rst.MoveLast
     rst.MoveFirst
     ReDim arr(rst.RecordCount - 1)
     Do While Not rst.EOF
         arr(rst.AbsolutePosition) = rst.Fields(0).Value
         rst.MoveNext
     Loop
     RecordsetInArrayEindimensional = arr
End Function

Listing 3: Einlesen eines eindimensionalen Arrays mit vorheriger Dimensionierung

Im Gegensatz zum ReDimensionieren innerhalb der Do While-Schleife entfällt hier natürlich das Preserve-Schlüsselwort der ReDim-Anweisung, denn das Array ist ja ohnehin noch leer.

Gleichzeitig beheben wir noch einen kleinen Fehler bei der Dimensionierung: Dort haben wir nämlich zunächst den Wert von rst.RecordCount als Obergrenze angegeben. Die Untergrenze ist jedoch, wenn nicht anders angegeben, 0. Das heißt, dass wir das Array um ein Element zu groß dimensionieren würden. Also ändern wir den Ausdruck für die obere Grenze in rst.RecordCount – 1.

Mehrdimensionale Arrays mit Daten füllen

Wenn Sie nun mehrere Felder in das Array einlesen möchten, müssen Sie dieses entsprechend anders dimensionieren – nämlich als zweidimensionales Array. Dazu deklarieren wir in der Funktion RecordsetInArrayMehrdimensional zunächst wieder das Array arr (s. Listing 4).

Public Function RecordsetInArrayMehrdimensional(rst As DAO.Recordset) As Variant()
     Dim arr() As Variant
     Dim fld As DAO.Field
     Dim i As Integer
     rst.MoveLast
     rst.MoveFirst
     ReDim arr(rst.RecordCount - 1, rst.Fields.Count - 1)
     Do While Not rst.EOF
         For Each fld In rst.Fields
             arr(rst.AbsolutePosition, i) = fld.Value
             i = i + 1
         Next fld
         rst.MoveNext
     Loop
     RecordsetInArrayMehrdimensional = arr
End Function

Listing 4: Einlesen eines mehrdimensionalen Arrays mit vorheriger Dimensionierung

Anschließend redimensioniert die Funktion es, und zwar mit der Anzahl der Datensätze in der ersten Dimension und mit der Anzahl der Felder in der zweiten. Danach durchläuft die Funktion in einer äußeren Do While-Schleife die Datensätze des Recordsets und in einer inneren Schleife die Felder – und zwar über die Fields-Auflistung des Recordsets. Innerhalb der Schleife füllt die Funktion das Array. Da fld keinen Index mitliefert, müssen wir immer noch umständlich eine Zählervariable namens i mitlaufen lassen, um die Position für die zweite Dimension anzugeben. Dies können wir auch noch eleganter erledigen, indem wir die innere Schleife als For…Next– statt als For…Each-Schleife implementieren:

Do While Not rst.EOF
     For i = 0 To rst.Fields.Count - 1
         arr(rst.AbsolutePosition, i) _
             = rst.Fields(i)
     Next i
     rst.MoveNext
Loop

Die Funktion zum Testen dieser Funktion finden Sie in Listing 5. Die Prozedur erstellt diesmal ein Recordset mit den beiden Feldern ArtikelID und Artikelname der Tabelle tblArtikel und übergibt dieses als Parameter an die Funktion RecordsetInArrayMehrdimensional.

Public Sub Test_RecordsetInArrayMehrdimensional()
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim i As Integer
     Dim j As Integer
     Dim arr() As Variant
     Set db = CurrentDb
     Set rst = db.OpenRecordset("SELECT ArtikelID, Artikelname FROM tblArtikel", _
         dbOpenDynaset)
     arr = RecordsetInArrayMehrdimensional(rst)
     For i = LBound(arr, 1) To UBound(arr, 1)
         For j = LBound(arr, 2) To UBound(arr, 2)
             Debug.Print arr(i, j);
         Next j
         Debug.Print
     Next i
End Sub

Listing 5: Testprozedur zum Einlesen mehrdimensionaler Arrays

Das Ergebnis durchläuft die Prozedur dann in zwei verschachtelten For…Next-Schleifen. Bei der Ermittlung der Arraygrenzen der beiden Dimensionen verwenden wir natürlich eine andere Form der LBound– und UBound-Funktionen. Die untere Grenze der ersten Dimension ermitteln wir beispielsweise mit LBound(arr, 1), die der zweiten Dimension mit LBound(arr, 2).

Innerhalb der inneren Schleife sollen die beiden Feldwerte des aktuellen Datensatzes ausgegeben werden. Damit diese in einer Zeile erscheinen, versehen wir die Debug.Print-Anweisungen noch mit einem folgenden Semikolon (;). Dies verhindert den Sprung in die folgende Zeile.

Allerdings würden so alle Elemente hintereinander in eine einzige Zeile geschrieben werden. Also müssen wir für jeden Datensatz, der ja in der äußeren Schleife durchlaufen wird, noch eine Debug.Print-Anweisung ohne Parameter einfügen.

Funktion zur Ausgabe von Arrays

Damit haben wir nun zwei Funktionen geschaffen, mit denen wir Arrays etwa auf Basis eines Recordsets füllen können. Allein die Ausgabe zur Prüfung des Arrays mit dem Durchlaufen der Elemente der einzelnen Dimensionen steckt noch in den beiden Testprozeduren.

Dabei wäre doch gerade die Ausgabe des Inhalts eines Arrays bei Entwickeln hilfreich, denn damit könnte man auf die Schnelle herausfinden, ob das Array auch die erwarteten Daten enthält. Also programmieren wir uns eine kleine Funktion, welche ein Array als Parameter erwartet und dieses im Direktbereich von Access ausgibt.

Die Prozedur heißt OutputVariant und ist in Listing 6 zu finden. Die Prozedur deklariert zwei Variablen namens i und j zum Durchlaufen der verschiedenen Dimensionen des Arrays sowie vier Variablen namens int1Min, int1Max, int2Min und int2Max, um die Unter- und Obergrenzen der Dimensionen des Arrays aufzunehmen.

Public Sub OutputVariant(varArray() As Variant)
     Dim i As Integer
     Dim j As Integer
     Dim int1Min As Integer
     Dim int1Max As Integer
     Dim int2Min As Integer
     Dim int2Max As Integer
     On Error Resume Next
     int1Min = LBound(varArray, 1)
     int1Max = UBound(varArray, 1)
     int2Min = LBound(varArray, 2)
     int2Max = UBound(varArray, 2)
     On Error GoTo 0
     If int2Max = 0 Then
         For i = int1Min To int1Max
             Debug.Print varArray(i);
         Next i
         Debug.Print
     Else
         For i = int1Min To int1Max
             For j = int2Min To int2Max
                 If varArray(i, j) = "" Then
                     Debug.Print " x ";
                 Else
                    Debug.Print varArray(i, j);
                 End If
             Next j
             Debug.Print
         Next i
     End If
End Sub

Listing 6: Prozedur zur Ausgabe von Arrays

Diese Variablen füllt die Prozedur in den folgenden vier Zeilen, für die durch die Anweisung On Error Resume Next die Fehlerbehandlung deaktiviert wurde. Dies hat den Hintergrund, dass die Anzahl der Dimensionen ja auch eins sein kann. In diesem Fall führt der Zugriff etwa auf LBound(varArray, 2) zum Fehler Index außerhalb des gültigen Bereichs.

In diesem Fall behält die Variable int2Max den Wert 0, was im nächsten Schritt geprüft wird. Dann liegt offensichtlich ein eindimensionales Array vor, das innerhalb einer einfachen For…Next-Schleife über die Werte von int1Min bis int1Max ausgegeben werden kann.

Anderenfalls liegt wohl ein mehrdimensionales Array vor, für dessen Ausgabe zwei For…Next-Schleifen mit den Laufvariablen i und j durchlaufen werden.

Um leere Inhalte besser erkennen zu können, sollen diese durch ein x ersetzt werden. Dies können Sie gegebenenfalls ändern, wenn Sie ein anderes oder gar kein Zeichen wünschen. Auch hier wenden wir wieder den Trick mit dem Semikolon (;) hinter der Debug.Print-Anweisung an, um die Elemente der zweiten Dimension in einer Zeile auszugeben.

Diese Routine hat allerdings eine Einschränkung, die mit dem Datentyp des Arrays zusammenhängt. Wenn Sie es einmal mit der folgenden Prozedur testen, die ein Array mit dem Datentyp Integer füllt und dieses dann der Routine OutputVariant übergibt, erhalten wir den Fehler aus Bild 2:

Fehler bei falschen Array-Datentyp

Bild 2: Fehler bei falschen Array-Datentyp

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