PDF ansehen - Access [basics]

Werbung
ACCESS
Datenzugriff programmieren
Verknüpfte Daten kopieren
basicS
Verknüpfte Daten kopieren
Das Kopieren einfacher Datensätze ist schnell erledigt. Markieren, kopieren, einfügen – schon liegt
der neue Datensatz vor. Was aber geschieht, wenn an dem zu kopierenden Datensatz noch weitere
Daten hängen wie etwa solche aus verknüpften Tabellen? Dann gilt es erst einmal, die Beziehung zu
prüfen und dann zu entscheiden, ob die verknüpften Daten ebenfalls dupliziert werden müssen. Und
schließlich benötigen Sie auch noch etwas VBA-Code, um die verknüpften Daten in einem Rutsch zu
kopieren. All dies finden Sie im vorliegenden Artikel.
Beispieldatenbank
Die Beispiele dieses Artikels finden
Sie in der Datenbank 1401_VerknuepfteDatenKopieren.mdb.
Daten aus verknüpften Tabellen
kopieren
Bevor wir uns auf die Programmierung der VBA-Routinen stürzen,
wollen wir uns erst einmal ansehen,
welche Konstellationen verknüpfter
Daten es gibt und wann überhaupt
auch die Inhalte der verknüpften
Tabellen kopiert werden müssen.
Der einfachste Fall liegt vor, wenn
etwa eine Tabelle namens tblKunden über das
Fremdschlüsselfeld
AnredeID mit einem
Datensatz der
Tabelle tblAnreden
verknüpft ist (siehe
Bild 1).
Die Tabelle tblAnreden enthält jede
benötigte Anrede
einmal, was schon
darauf hindeutet,
dass beim Kopieren
eines Datensatzes
einer Kundentabelle kein neuer
Datensatz in der
www.access-basics.de
verknüpften Tabelle
tblAnreden angelegt
werden muss. Und so
ist es: Sie kopieren einfach den Kundendatensatz, wobei der neue
Datensatz im Fremdschlüsselfeld AnredeID
den gleichen Verweis
auf einen Datensatz
der Tabelle tblAnreden
enthält wie der Originaldatensatz.
Bild 1: Beziehung zwischen Kunden und Anreden
Dies gilt eigentlich für alle Beziehungen, in denen die über ein Fremdschlüsselfeld referenzierte Tabelle
lediglich Lookup-Werte für ein Feld
der Haupttabelle liefert – also beispielsweise auch für Tabellen wie
tblKategorien, tblGeschlecht et
cetera. Dies kann
sich aber auch auf
Tabellen beziehen,
die mehr als nur einen
Lookup-Wert liefern,
also beispielsweise
einer Tabelle wie
tblLieferanten, die
etwa mit einer Tabelle
namens tblArtikel
verknüpft ist.
Individuelle
Rechnungen
Bild 2: Rechnungen und Rechnungspositionen
Anders sieht es aus,
wenn die verknüpfte Tabelle Daten
enthält, die erst in
Seite 15
ACCESS
Datenzugriff programmieren
Verknüpfte Daten kopieren
basicS
Zusammenhang mit dem Datensatz
der Haupttabelle angelegt werden.
Wenn Sie also etwa eine Rechnung
zu einem Auftrag in der Tabelle
tblRechnungen anlegen und dazu
einige Rechnungspositionen in der
damit verknüpften Tabelle tblRechnungspositionen, ändert sich die
Situation (siehe Bild 2): Wenn Sie einen Rechnung kopieren, werden Sie
wohl auch die Rechnungspositionen
kopieren, um diese gegebenenfalls
individuell anpassen zu können.
Bild 3: Bestellungen, Bestellpositionen und Artikel
In diesem Fall gehen wir davon aus,
dass es sich um Rechnungen für
individuelle Leistungen handelt – also
beispielsweise das Programmieren
einer Anwendung für einen Kunden.
Und trotz aller Individualität: Wenn es
sich um ein größeres Projekt handelt,
werden Sie möglicherweise immer
wieder ähnliche Positionen in Rechnung stellen, die Sie nicht jedes Mal
erneut formulieren möchten. Dies ist
der erste Fall, den wir uns in diesem
Artikel anschauen werden.
Bestellungen und Artikel
Der zweite Fall ist die klassische Bestellung mit Bestellpositionen und Artikeln. Hier werden die Bestellungen
und die Artikel über eine Tabelle etwa
namens tblBestelldetails miteinander
per m:n-Beziehung verknüpft.
Wenn Sie hier eine Bestellung neu
auf einer bereits vorhandenen Bestellung anlegen möchten, welche die
gleichen Bestellpositionen wie das
Original enthält, werden Sie sich
freuen, wenn Sie dies per Mausklick
erledigen können statt die Bestellung
manuell Bestellposition für Bestellposition anzulegen. Dies ist das zweite
www.access-basics.de
Bild 4: Formular zum Erstellen individueller Rechnungen
in diesem Artikel behandelte Beispiel
(siehe Bild 3).
Daten aus 1:n-Beziehungen
kopieren
Nun wollen wir uns das erste Beispiel
der individuellen Rechnungen ansehen. Den relevanten Teil des Datenmodells haben Sie ja bereits weiter
oben kennengelernt. Zusätzlich
haben wir ein Formular erstellt, dass
die Daten der Tabelle tblRechnungen anzeigt und die Daten der per
1:n-Beziehung verknüpften Tabelle
tblRechnungspositionen zur aktuellen Rechnung in einem Unterformular
anzeigt (siehe Bild 4). Haupt- und
Unterformular sind über den Wert
RechnungID in den Eigenschaften
Verknüpfen von und Verknüpfen
nach des Unterformular-Steuerelements miteinander verknüpft.
Wenn Sie nun für den gleichen Kunden eine neue Rechnung erstellen,
können Sie dies natürlich manuell
Seite 16
ACCESS
Datenzugriff programmieren
Verknüpfte Daten kopieren
basicS
erledigen. Wenn sich jedoch weder der Rechnungsbetreff noch die
enthaltenen Rechnungspositionen
nicht wesentlich geändert haben,
können Sie auch den vorhandenen
Rechnungsdatensatz kopieren – und
ebenso die damit verknüpften Rechnungspositionen.
Dazu haben wir oben im Formular
eine Schaltfläche namens cmdRechnungKopieren angelegt. Die durch
das Anklicken dieser Schaltfläche
ausgelöste Ereignisprozedur soll nun
zwei Aufgaben erledigen:
• Kopieren des Datensatzes aus
der Tabelle tblRechnung (also
der im Hauptformular angezeigte
Datensatz) und
• Kopieren der mit dem Datensatz
im Hauptformular verknüpften
Datensätze der Tabelle tblRechnungspositionen (also der Datensätze im Unterformular).
Es gibt (mindestens) zwei Möglichkeiten, diese Aufgabe zu erledigen:
• durch das Anlegen der neuen Datensätze mit den DAO-Methoden
AddNew/Update oder
• mit entsprechenden SQL-Anweisungen.
Kopieren per DAO
Die erste Variante sieht wie in
Listing 1 aus und kopiert die Da-
Private Sub cmdRechnungKopieren_Click()
Dim db As DAO.Database
Dim rstRechnungen As DAO.Recordset
Dim rstPositionenAlt As DAO.Recordset
Dim rstPositionenNeu As DAO.Recordset
Dim lngAlteRechnungID As Long
Dim lngNeueRechnungID As Long
Set db = CurrentDb
lngAlteRechnungID = Me!RechnungID
Set rstRechnungen = db.OpenRecordset("SELECT * FROM tblRechnungen WHERE 1=2", dbOpenDynaset)
With rstRechnungen
.AddNew
!Rechnungsbetreff = Me!Rechnungsbetreff
!Rechnungstext = Me!Rechnungstext
!Bemerkungen = Me!Bemerkungen
!KundeID = Me!KundeID
!Rechnungsdatum = Me!Rechnungsdatum
lngNeueRechnungID = !RechnungID
.Update
End With
Set rstPositionenAlt = db.OpenRecordset("SELECT * FROM tblRechnungspositionen WHERE RechnungID = " & lngAlteRechnungID, dbOpenDynaset)
Set rstPositionenNeu = db.OpenRecordset("SELECT * FROM tblRechnungspositionen WHERE 1=2", dbOpenDynaset)
With rstPositionenAlt
Do While Not .EOF
rstPositionenNeu.AddNew
rstPositionenNeu!RechnungID = lngNeueRechnungID
rstPositionenNeu!Rechnungsposition = !Rechnungsposition
rstPositionenNeu!Menge = !Menge
rstPositionenNeu!Preis = !Preis
rstPositionenNeu!Mehrwertsteuer = !Mehrwertsteuer
rstPositionenNeu!EinheitID = !EinheitID
rstPositionenNeu.Update
.MoveNext
Loop
End With
Me.Requery
Me.Recordset.FindFirst "RechnungID = " & lngNeueRechnungID
End Sub
Listing 1: Kopieren von Rechnungsdaten aus dem Haupt- und dem Unterformular
www.access-basics.de
Seite 17
ACCESS
Datenzugriff programmieren
Verknüpfte Daten kopieren
basicS
ten mit AddNew/Update. Dazu ist
offensichtlich eine Menge Code
erforderlich, aber wir wollen ja auch
gleich die Daten aus zwei verknüpften Tabellen duplizieren.
Um diese Prozedur anzulegen, klicken Sie in der Entwurfsansicht des
Formulars frmRechnungen auf die
Schaltfläche cmdRechnungKopieren
und wählen dann im Eigenschaftsfenster für die Eigenschaft Beim
Klicken den Eintrag [Ereignisprozedur] aus.
Wenn Sie dann rechts auf die Schaltfläche mit den drei Punkten (...) klicken, zeigt der VBA-Editor ein neues
Klassenformular für den Formularcode mit der benötigten Prozedur
an. Diese füllen Sie dann mit dem
nachfolgend beschriebenen Code.
Die Prozedur speichert zunächst
den Primärschlüsselwert des aktuellen Datensatzes im Formular in der
Variablen lngAlteRechnungID und
erstellt ein Recordset auf Basis der
Tabelle tblRechnungen, wobei dieses allerdings keine Daten enthalten
soll (Bedingung WHERE 1=2). Dieser
Datensatzgruppe fügt die Prozedur
mit der AddNew-Methode einen
neuen Datensatz hinzu. Dann stellt
sie die einzelnen Felder des neuen
Datensatzes auf die entsprechenden
Werte der Steuerelemente im Formular ein.
der Kopien der Rechnungspositionen
der Originalrechnung. Daher stellen
wir auch hier als Kriterium den Ausdruck 1=2 ein.
Nun speichert sie den Primärschlüsselwert des neuen Datensatzes (also
den Wert des Feldes RechnungID), der eigentlich schon nach der
Ausführung der AddNew-Methode
vorliegt, in der Variablen lngNeueRechnungID – wir benötigen diesen
Wert gleich noch für mehrere Aktionen. Schließlich speichert die Prozedur den neuen Datensatz mit der
Update-Methode.
Nun durchläuft die Prozedur alle Datensätze des ersten Recordsets mit
den Original-Rechnungspositionen.
Sie legt für jede Rechnungsposition
zunächst einen neuen Datensatz
an. Wichtig ist hier die Zeile, die das
Feld RechnungID füllt: Dieses erhält
nämlich mit dem in der Variablen
lngNeue­RechnungID gespeicherten
Wert den Wert des Primärschlüsselfeldes des soeben angelegten
Datensatzes in der Tabelle tblRechnungen. Die übrigen Feldinhalt werden einfach von den entsprechenden
Feldern des aktuellen Datensatzes
des Recordsets rstPositionenAlt
übernommen.
Nun füllt die Prozedur zwei weitere
Recordset-Variablen mit Verweisen
auf neu geöffnete Recordsets. Das
erste heißt rstPositionenAlt und
enthält alle Datensätze der Tabelle
tblRechnungspositionen, deren Feld
RechnungID den in lngAlteRechnungID gespeicherten Wert enthält.
Dies entspricht allen Datensätzen der
Tabelle tblRechnungspositionen, die
mit dem zuvor kopierten Datensatz
der Tabelle tblRechnungen verknüpft
sind. Die zweite Datensatzgruppe
basiert ebenfalls auf der Tabelle tblRechnungspositionen, soll aber leer
sein und dient lediglich dem Anlegen
Schließlich speichert die Routine den
neuen Datensatz mit der UpdateMethode des Recordset-Objekts und
wechselt mit der MoveNext-Methode
zum nächsten Datensatz – und dies
wird solange wiederholt, solange die
Abbruchbedingung in der Do WhileSchleife (rstPositionenAlt.EOF) nicht
erfüllt ist.
Schließlich zeigt die Prozedur den
neu angelegten Rechnungsdatensatz
im Hauptformular an, wobei erneut
der in lngRechnungID gespeicherte
Primärschlüsselwert des neuen Datensatzes zum Einsatz kommt.
Verknüpfte Daten per SQL
kopieren
Bild 5: Neue Schaltfläche zum Kopieren per SQL-Anweisung
www.access-basics.de
Wir wollen uns noch eine zweite Variante zum Kopieren der Datensätze
ansehen. Diese basiert auf der Ver-
Seite 18
ACCESS
Datenzugriff programmieren
Verknüpfte Daten kopieren
basicS
Private Sub cmdRechnungKopierenSQL_Click()
Dim db As DAO.Database
Dim lngRechnungID As Long
Dim strSQL As String
Set db = CurrentDb
strSQL = "INSERT INTO tblRechnungen SELECT Rechnungsbetreff, Rechnungstext, Bemerkungen, KundeID FROM tblRechnungen " _
& "WHERE RechnungID = " & Me!RechnungID
db.Execute strSQL, dbFailOnError
If db.RecordsAffected = 1 Then
lngRechnungID = db.OpenRecordset("SELECT @@IDENTITY").Fields(0)
strSQL = "INSERT INTO tblRechnungspositionen SELECT Rechnungsposition, Menge, Preis, Mehrwertsteuer, EinheitID, " _
& lngRechnungID & " AS RechnungID FROM tblRechnungspositionen WHERE RechnungID = " & Me!RechnungID
db.Execute strSQL, dbFailOnError
End If
Me.Requery
Me.Recordset.FindFirst "RechnungID = " & lngRechnungID
End Sub
Listing 2: Kopieren der verknüpften Datensätze per INSERT INTO-Anweisung
wendung der INSERT INTO-Anweisung, die mit der Execute-Methode
des Database-Objekts ausgeführt
wird.
Dazu fügen wir dem Formular frmRechnungen eine weitere Schaltfläche namens cmdRechnungKopieren
hinzu (siehe Bild 5).
Diese löst die Prozedur aus Listing 2
aus. Die Prozedur stellt zunächst
eine die zum Kopieren des Rechnungsdatensatzes benötigte SQLAnweisung zusammen und speichert
diese in der Variablen strSQL. Diese
führt die Prozedur in der folgenden
Zeile mit der Methode Execute des
Database-Objekts aus.
Die folgende If...Then-Bedingung prüft mit dem Ausdruck
db.RecordsAffected = 1, ob die
zuvor ausgeführte Aktionsabfrage
genau einen Datensatz betroffen
hat. Ist dies der Fall, wurde der neue
Datensatz erfolgreich angelegt und
wir können mit dem Kopieren der mit
dieser Rechnung verknüpften Rechnungspositionen fortfahren.
www.access-basics.de
Innerhalb der If...Then-Bedingung ermittelt die Prozedur mit der Abfrage
SELECT @@IDENTITY den zuletzt in
der aktuellen Sitzung hinzugefügten
Primärschlüsselwert. Damit ausgestattet, können wir in der folgenden
Zeile die SQL-Anweisung definieren,
die alle Datensätze der Tabelle tblRechnungspositionen, die mit dem
zu kopierenden Datensatz der Tabelle tblRechnungen verknüpft sind,
mit dem in der Variablen lngNeueRechnungID gespeicherten Wert im
Fremdschlüsselfeld RechnungID als
neue Datensätze in die Tabelle tblRechnungspositionen kopiert. Auch
diese SQL-Anweisung führen wir
mithilfe der Execute-Methode aus.
tensätzen liegt sicher darin, dass wir
statt der AddNew/Update-Methoden
des Recordset-Objekts nun die
Execute-Methode mit einer entsprechenden INSERT INTO-Aktionsabfrage verwendet haben.
Ein weiterer Unterschied ist, dass wir
bei der DAO-Methode das neue Recordset der Tabelle tblRechnungen
mit den Daten des aktuell angezeigten Datensatzes im Formular gefüllt
haben:
With rstRechnungen
.AddNew
!Rechnungsbetreff = µ
Me!Rechnungsbetreff
!Rechnungstext = Me!Rechnungstext
Anschließend sorgt die Requery-Methode des aktuellen Formulars noch
für das Neuladen der Daten für den
aktuellen Datensatz und springt dann
zu dem neu angelegten Datensatz
der Tabelle tblRechnungen.
Unterschiede
Der große Unterschied zwischen den
beiden zuvor verwendeten Methoden
zum Kopieren von verknüpften Da-
!Bemerkungen = Me!Bemerkungen
!KundeID = Me!KundeID
lngNeueRechnungID = !RechnungID
.Update
End With
Im zweiten Beispiel mit der SQL-Aktionsabfrage haben wir die notwendige Anweisung so zusammengesetzt,
dass diese die zu kopierenden Werte
direkt aus der Tabelle tblRechnun-
Seite 19
ACCESS
Datenzugriff programmieren
Verknüpfte Daten kopieren
basicS
Private Sub cmdRechnungKopierenSQL_Click()
...
strSQL = "INSERT INTO tblRechnungen(Rechnungsbetreff, Rechnungstext, Bemerkungen, Rechnungsdatum, KundeID) VALUES('" _
& Me!Rechnungsbetreff & "', '" & Me!Rechnungstext & "', '" & Me!Bemerkungen & "', " & SQLDatum(Me!Rechnungsdatum) & ", " _
& Me!KundeID & ")"
...
End Sub
Listing 3: INSERT INTO-Anweisung mit VALUES-Schlüsselwort
gen bezieht, und zwar für den
Datensatz, den wir mit dem Kriterium "...WHERE RechnungID = " &
Me!Rechnung ermittelt haben:
strSQL = "INSERT INTO tblRechnungen µ
SELECT Rechnungsbetreff, µ
de Positionen in Rechnung:', 'Mit
freundlichen Grüßen André Minhorst',
#2014/01/14#, 2)
Nun wundern Sie sich vielleicht, was
aus der Datumsangabe 14.1.2014
geworden ist.
Rechnungstext, Bemerkungen, µ
KundeID FROM tblRechnungen µ
WHERE RechnungID = " & Me!RechnungID
Wir hätten die Daten zum Einfügen
in die Tabelle tblRechnungen per
INSERT INTO auch direkt aus dem
Formular beziehen können. Dies
sieht dann, in Ausschnitten, wie in
Listing 3 aus.
Dort verwenden wir die VALUESSyntax, bei der wir hinter INSERT
INTO zunächst in Klammern die
Liste der Felder angeben, die gefüllt
werden sollen, und dann – wiederum in Klammern – die tatsächlichen
Werte als kommaseparierte Liste
hinterlegen. Eine solche Anweisung
sieht, mit konkreten Werten gefüllt,
beispielsweise wie folgt aus:
INSERT INTO
tblRechnungen(Rechnungsbetreff, Rechnungstext, Bemerkungen, Rechnungsdatum, KundeID) VALUES('Programmierung
"Access-Tools"', 'Sehr geehrter Herr
Müller, hiermit stellen wir folgen-
www.access-basics.de
Damit ein solches Datum von SQL
verarbeitet werden kann, müssen wir
es entsprechend aufbereiten – und
zwar beispielsweise durch die Darstellung in der Form #2014/1/14#.
Bestellung nicht berührt, sondern nur
referenziert - das heißt, dass man die
in der Tabelle tblArtikel enthaltenen
Datensätze auch nicht mitkopieren
muss.
Sie würden also in der oben genannten Konstellation den Datensatz der
Tabelle tblBestellungen kopieren
und alle Datensätze der Tabelle
tblBestellpositionen, die mit diesem
Datensatz verknüpft sind.
Dies erreichen Sie durch die Verwendung einer kleinen Hilfsfunktion
namens SQLDatum. Diese sieht wie
in Listing 4 aus.
Die Datensätze der Tabelle tblArtikel
braucht man in diesem Fall nicht zu
kopieren, da diese wohl kaum zum
Zwecke der Anpassung dupliziert
werden sollen.
Kopieren von Daten in m:nBeziehungen
Zusammenfassung und
Ausblick
Hinter dem Kopieren von Daten aus
m:n-Beziehungen verbirgt sich eine
ganz ähnliche Technik wie für das
Kopieren von 1:n-Daten, was ja auch
logisch ist. Wir betrachten dies für
die drei Tabellen tblBestellungen,
tblBestellpositionen und tblArtikel:
Das Kopieren verknüpfter Daten ist
manchmal recht hilfreich, vor allem,
wenn es um das Kopieren von Datensätzen geht, die man in der gleichen
oder einer leicht abgewandelten Form
benötigt – also zumindest so, dass es
weniger Aufwand bedeutet, die Daten
zu kopieren und anzupassen, als
diese manuell zu erstellen.
Die Artikel im n-Teil der Beziehung
werden
auch im
Public Function SQLDatum(varDate As Variant)
SQLDatum = Format(varDate, "\#yyyy\/mm\/dd#")
ZusamEnd Function
menhang
Listing 4: Hilfsfunktion zum Formatieren von Datumsangaben
mit der
Seite 20
Herunterladen