Viele Datenbank-Lösungen arbeiten mit einer Kombination aus einem Hauptdatensatz in einer Tabelle und einem oder mehreren Detaildatensätzen in einer zweiten Tabelle, wobei die Verknüpfung über einen gemeinsamen Ordnungsbegriff erfolgt. Beispiel Bestellungen: Eine Tabelle „Bestellungen“ enthält hier die Basisdaten wie Kunde, Bestellnummer, Versandform oder Lieferdatum, eine zweite Tabelle „Bestelldetails“ nimmt einen oder mehrere Sätze auf, in der die bestellten Artikel festgehalten sind. Die Verknüpfung erfolgt jeweils über die eindeutige Bestellnummer. Wenn Sie mit solchen Konstruktionen arbeiten, haben Sie sich bestimmt schon oft gewünscht, eine komplette Bestellung kopieren zu können, da ein Kunde gerade fast dieselbe Bestellung wie der Kunde vor ihm aufgegeben hat. Aber auch bei Kunden, die monatlich immer die gleichen Artikel bestellen, wäre eine Kopierfunktion recht hilfreich. Allerdings stellt Access keine Funktionen bereit, um sowohl Hauptdatensatz als auch die dazugehörigen Detaildatensätze kopieren und als neuen Vorgang einfügen zu können. Mit einer VBA-Routine schaffen Sie hier Abhilfe.
In unserer Beispiel-Datenbank DETAILS. finden Sie das Formular „Bestellungen“ mit dem Unterformular „Bestellungen-Unterformular“. Es basiert auf den Tabellen „Bestellungen“, deren Inhalt im Hauptformular angezeigt wird, und „Bestelldetails“, die im Unterformular erscheinen. Um hier einen kompletten Vorgang zu kopieren, sind also ein Datensatz aus „Bestellungen“ und ein oder mehrere Datensätze aus „Bestelldetails“ zu duplizieren, wobei bestimmte Feldinhalte wie „Bestell-, Versand-, Lieferdatum“ oder „Bestellnummer“ angepasst werden müssen.
Dazu enthält das Formular eine Schaltfläche „Bestellung kopieren“, die in der Ereignisprozedur „Beim Klicken“ die folgenden Anweisungen beinhaltet:
Private Sub btnKopieren_Click()
Dim lngBestNr As Long, lngNeueBestNr As Long, strSQL As String
Dim db As DATABASE, rs1 As Recordset, rs2 As Recordset
Dim lngAnzDS As Long, lngAnzFld As Long, I As Integer, J As Integer
On Error Resume Next
lngBestNr = Me.[Bestell-Nr]
If Err <> 0 Then ‚Neuer Satz…
Beep
Exit Sub
End If
On Error GoTo 0
Hier wird zunächst die aktuelle Bestellnummer aus dem Formular ausgelesen. Dabei kommt es eventuell zu einem Fehler, wenn gerade ein neuer Datensatz angelegt wurde und der Anwender aus Versehen auf die Schaltfläche geklickt hat. Die Routine gibt dann einen akustischen Hinweis aus und beendet die Ausführung der Ereignis-Prozedur über „Exit Sub“.
strSQL = „select * from [Bestellungen] where [Bestell-Nr]= “ & lngBestNr
Set db = CurrentDb()
Set rs1 = db.OpenRecordset(strSQL, dbOpenSnapshot)
Set rs2 = db.OpenRecordset(„Bestellungen“, dbOpenDynaset)
Anschließend werden zwei Recordsets geöffnet. „rs1“ ist der aktuelle Datensatz der Bestellung, „rs2“ repräsentiert die Tabelle „Bestellungen“ und wird gleich für das Anlegen eines neuen Datensatzes verwendet. Die Prozedur prüft dann, ob zur Bestellnummer ein Datensatz gefunden wurde. Wenn nicht, hat der Anwender vermutlich die Bestellnummer manuell geändert und der Datensatz wurde noch nicht gesichert. Nach einem Hinweis an den Anwender wird die Routine verlassen.
Sie ermitteln dann über „DMax()“ eine neue Bestellnummer und halten diese in der Variablen „lngNeueBestNr“ für die spätere Zuweisung fest. Dann legt „Add-New“ einen neuen, leeren Hauptdatensatz an, der die Kopie der aktuellen Basisdaten der Bestellung aufnimmt.
‚Daten aus altem Satz in neuen Satz schreiben
lngAnzFld = rs1.Fields.Count – 1
For I = 0 To lngAnzFld
Select Case rs1(I).Name
Case „Bestell-Nr“
rs2(I).Value = lngNeueBestNr
Case „Bestelldatum“, „Versanddatum“, „Lieferdatum“
rs2(I).Value = Now
Case Else
rs2(I).Value = rs1(I).Value
End Select
Next I
rs2.UPDATE
rs1.Close
rs2.Close
In For-Next-Schleifen übertragen wir die Feldinhalte aus dem alten in den neuen Hauptdatensatz. Dabei erfahren die Felder „Bestell-Nr“ und die Felder mit den Datumsangaben eine besondere Behandlung: Die Bestellnummer wird auf die zuvor ermittelte, neue Bestellnummer und die Datumsangaben werden auf das aktuelle Datum gesetzt.
Abschließend werden alle Recordsets geschlossen, da diese nicht mehr benötigt oder für die Übertragung der Detail-Datensätze neu initialisiert werden müssen.
‚— Detail-Datensätze —
strSQL = „select * from [Bestelldetails] where [Bestell-Nr]= “ & lngBestNr
Set rs1 = db.OpenRecordset(strSQL, dbOpenSnapshot)
Set rs2 = db.OpenRecordset(„Bestelldetails“, dbOpenDynaset)
On Error Resume Next
rs1.MoveLast
If Err <> 0 Then
Beep
MsgBox „Fehler bei Zugriff auf Bestelldetails für Bestellung “ & lngBestNr & „!“
GoTo Weiter
End If
lngAnzDS = rs1.RecordCount
rs1.MoveFirst
On Error GoTo 0
Hier werden die Recordsets für die Übertragung der Detail-Datensätze initialisiert und geprüft. Wurden keine Detaildatensätze gefunden, liegt vermutlich eine unvollständige Bestellung vor. Nach einem Hinweis an den Anwender brechen wir an dieser Stelle ab und zeigen weiter unten die neue Bestellung an. In zwei Schleifen kopieren wir dann die Detaildatensätze und deren einzelne Felder, wobei die Bestellnummer auf die zuvor ermittelte, neue Bestellnummer gesetzt wird.
Abschließend aktualisiert die Prozedur die Datenbasis über ein „Requery“, setzt den Fokus auf das Feld „Bestell-Nr“ und positioniert das Formular über „FindRecord“ auf die „neue“ Bestellung.
Hier noch mal die gesamte Programmierung:
Private Sub btnKopieren_Click()
Dim lngBestNr As Long, lngNeueBestNr As Long, strSQL As String
Dim db As DATABASE, rs1 As Recordset, rs2 As Recordset
Dim lngAnzDS As Long, lngAnzFld As Long, I As Integer, J As Integer
On Error Resume Next
lngBestNr = Me.[Bestell-Nr]
If Err <> 0 Then ‚Neuer Satz…
Beep
Exit Sub
End If
On Error GoTo 0
strSQL = „select * from [Bestellungen] where [Bestell-Nr]= “ & lngBestNr
Set db = CurrentDb()
Set rs1 = db.OpenRecordset(strSQL, dbOpenSnapshot)
Set rs2 = db.OpenRecordset(„Bestellungen“, dbOpenDynaset)
On Error Resume Next
rs1.MoveFirst
If Err <> 0 Then
Beep
MsgBox „Fehler bei Zugriff auf Bestellung “ & lngBestNr & „!“
rs1.Close
Exit Sub
End If
On Error GoTo 0
‚— Haupt-Datensatz —
’neue Bestell-Nr ermitteln
lngNeueBestNr = DMax(„[Bestell-Nr]“, „Bestellungen“) + 1
‚Neuen Datensatz anlegen
rs2.AddNew
‚Daten aus altem Satz in neuen Satz schreiben
lngAnzFld = rs1.Fields.Count – 1
For I = 0 To lngAnzFld
Select Case rs1(I).Name
Case „Bestell-Nr“
rs2(I).Value = lngNeueBestNr
Case „Bestelldatum“, „Versanddatum“, „Lieferdatum“
rs2(I).Value = Now
Case Else
rs2(I).Value = rs1(I).Value
End Select
Next I
rs2.UPDATE
rs1.Close
rs2.Close
‚— Detail-Datensätze —
strSQL = „select * from [Bestelldetails] where [Bestell-Nr]= “ & lngBestNr
Set rs1 = db.OpenRecordset(strSQL, dbOpenSnapshot)
Set rs2 = db.OpenRecordset(„Bestelldetails“, dbOpenDynaset)
On Error Resume Next
rs1.MoveLast
If Err <> 0 Then
Beep
MsgBox „Fehler bei Zugriff auf Bestelldetails für Bestellung “ & lngBestNr & „!“
GoTo Weiter
End If
lngAnzDS = rs1.RecordCount
rs1.MoveFirst
On Error GoTo 0
For J = 1 To lngAnzDS
‚Daten aus altem Satz in neuen Satz schreiben
rs2.AddNew
lngAnzFld = rs1.Fields.Count – 1
For I = 0 To lngAnzFld
If rs1(I).Name <> „Bestell-Nr“ Then
rs2(I).Value = rs1(I).Value
Else
rs2(I).Value = lngNeueBestNr
End If
Next I
rs2.UPDATE
rs1.MoveNext
Next J
Weiter:
On Error Resume Next
rs1.Close
rs2.Close
Me.Requery
Me.[Bestell-Nr].SetFocus
DoCmd.FindRecord lngNeueBestNr
End Sub