Das Access-Magazin für alle, die schnell von 0 auf
Transcription
Das Access-Magazin für alle, die schnell von 0 auf
Access [basics] www.access-basics.de Das Access-Magazin für alle, die schnell von 0 auf 100 wollen Abfragen für die Datenauswahl Teil III: Filtern von Textfeldern Im vorliegenden und den folgenden Teilen dieser Artikelserie zum Thema Abfragen beschäftigen wir uns mit den verschiedenen Möglichkeiten zum Auswählen von Daten über verschiedene Kriterien. Den Start machen einfache Textkriterien. Sie erinnern sich noch an den Chef unserer kleinen Beispielfirma Schwuppdiwupp? Der saß doch am Wochenende immer am Computer und hat die Daten der verschiedenen Mitarbeiter zusammengeführt. Inzwischen hat ihm sein neuer Mitarbeiter die Daten ja in Access-Tabellen zusammengefasst, aber so richtig glücklich ist er noch nicht. Jetzt will er doch wieder einzelne Daten sehen... Beispielsweise konnte er früher einfach in der jeweiligen Excel-Tabelle nachgucken, welche Projekte Tabea Neukirch bearbeitet hat. Jetzt stehen alle Mitarbeiter zusammen in der gleichen Tabelle und er muss die erst wieder herausfiltern. Dabei ist das gar nicht so schwierig: in 03/2010 haben wir schon gezeigt, wie so eine Auswahlabfrage aussieht. Es geht aber noch besser. Basisabfrage erstellen Erstellen Sie also zuerst einmal eine neue Abfrage mit den drei Tabellen tblProjekte, tblMitarbeiterProjekte und tblMitarbeiter. Aus diesen fügen Sie per Doppelklick die Felder Projektbezeichnung, Vorname und Nachname zum Entwurfsraster der Abfrage hinzu. Auch wenn Sie aus der Tabelle tblMitarbeiterProjekte selber gar kein Feld brauchen, erinnern Sie sich sicher, dass diese als Verbindung zwischen den beiden anderen Tabellen notwendig ist (siehe auch Tabellen entwerfen - Teil III: Mitarbeiter per m:nBeziehung zu Projektteams zusammenstellen). Was Sie in diesem Artikel lernen »» Textfelder nach einfachen Zeichenketten filtern »» Platzhalter für das Filtern einsetzen »» Und- und Oder-Verknüpfungen verwenden Abfragen im Griff Diese Ausgabe von Access [basics] stellt einerseits das Thema Abfrage und andererseits eine Lösung zum Verwalten von m:n-Beziehungen mit Listenfeldern in den Mittelpunkt. Nebenher lernen Sie noch den Umgang mit VBA-Variablen kennen. Doch zunächst zu den Abfragen: Zu diesem Thema finden Sie gleich drei Artikel. Im ersten zeigen wir Ihnen, wie Sie die Datensätze der Abfrage nach dem Inhalt von Textfeldern filten können. Es geht allerdings nicht nur um den einfachern Vergleich mit Zeichenketten, denn wir nehmen auch Spezialitäten wie die Platzhalter unter die Lupe. Außerdem erfahren Sie, wie Sie verschiedene Kriterien mit Operatoren wie OR oder AND verknüpfen können. Der zweite Artikel zum Thema Abfragen beschäftigt sich mit Abfrageparametern: Sie können eine Abfrage nämlich so anlegen, dass der Benutzer beim Ausführen selbst die Kriterien festlegen kann – ohne selbst den Entwurf zu ändern! Und schließlich schauen wir uns an, wie Sie Daten nach Zahlenfeldern filtern können – einen kleinen Abstecher in die Welt der Unterabfragen inklusive. Wer mit VBA programmiert, kommt selten ohne den Einsatz von Variablen aus. Dabei handelt es sich um Platzhalter für Werte, die während des Ablaufs einer VBA-Routine gefüllt und auch wieder ausgelesen werden können. Alles zu diesem Thema finden Sie hier. Und schließlich wollen wir am Beispiel der Projektzeiterfassung zeigen, wie Sie die Werte aus per m:n-Beziehung verknüpften Tabellen mit Hilfe eines einfachen Formulars und zweier Listenfelder verwalten können. Viel Spaß beim Lesen! [André Minhorst] In dieser Ausgabe: Abfragen für die D atenauswahl - Teil III: Filtern von Textfeldern Abfragen für die D atenauswahl – Teil IV: Einsatz von Parametern Abfragen für die Datenauswahl – Teil V: Filtern nach Zahlen VBA-Programmierung – Teil II: Variablen in VBA Formulare für die Datenbearbeitung – Teil V: m:nBeziehungen in Listenfeldern darstellen ISSN: 2190-8761 Access [basics] 8/2010 www.access-basics.de der Nachname eben nicht eindeutig ist, müssen Sie mit dem Vornamen ein weiteres Kriterium hinzufügen. Es ist übrigens wichtig, dass der Vorname als zweites Kriterium in der gleichen Zeile steht. Nur dann liefert die Abfrage alle Datensätze, die beide Kriterien gleichzeitig erfüllen. Und-Verknüpfung Bild 1: Der Entwurf der Abfrage, um die Projekte bestimmter Mitarbeiter zu finden Bild 2: Das Abfrage-Ergebnis für Tabea Neukirch Kriterien definieren Jetzt tragen Sie nur noch den Wert Tabea als Kriterium in die Spalte Vorname und Neukirch in die Spalte Nachname ein und die Abfrage ist fertig. Sie können diese direkt als qryProjekteMitMitarbeiternTabea speichern (s. Bild 1). Access erkennt automatisch, dass es sich bei beiden Feldern um Textfelder handelt. Daher werden die Kriterien beim Verlassen der Zelle oder beim Speichern der Abfrage automatisch in Gänsefüßchen gesetzt, damit es syntaktisch korrekt ist. Das nennt sich in mathematischer Logik eine UND-Verknüpfung: Beide Kriterien müssen gleichzeitig erfüllt werden. Leider spielt uns da unser täglicher Umgang mit der Sprache einen Streich. Wenn Frau Köhler aus unserer Beispielfirma morgens in der Küche erzählt, dass Sie am Wochenende in einem Konzert mit Musik von Bach und Bartók gewesen war, dann mag sie das für einen unvergesslichen Abend halten. Ein Logiker hingegen wundert sich, denn Béla Bartók wurde erst rund 200 Jahre nach Johann Sebastian Bach geboren. Wie können die beiden zusammen Musik komponiert haben? Oder wurden im Konzert die Stücke der beiden Komponisten gleichzeitig gespielt? Das muss ja furchtbar geklungen haben! Oder-Verknüpfung Selbstverständlich meinte Frau Köhler nicht, dass die Musik von Bach und Bartók war, sondern dass es entweder ein Stück von Bach oder von Bartók war. Das sprachliche „und“ ist im logischen Sinne in den meisten Fällen ein ODER. Warum das hier so wichtig ist? Na, dann suchen Sie doch mal nach den Projekten der Kollegen Fischer und Schneider! Wenn der Chef nun diese Abfrage ausführt, sieht er tatsächlich die beiden Projekte, an denen die Mitarbeiterin arbeitet (s. Bild 2). Es ist wichtig, dass Sie sowohl den Vor- als auch den Nachnamen in den Filterkriterien angeben. Lassen Sie das Kriterium Tabea für Vorname weg, sehen Sie insgesamt vier Ergebnisse (s. Bild 3). Da auch Tabeas Bruder Erich in der Firma arbeitet, werden nun die Projekte der beiden angezeigt. Da Seite 2 Bild 3: Das Abfrage-Ergebnis nur für Neukirch Access [basics] 8/2010 www.access-basics.de Mal abgesehen davon, dass wir derzeit noch gar keine Möglichkeit kennen, zwei Kriterien für das gleiche Feld unterzubringen, wäre es falsch. In Wirklichkeit suchen Sie nämlich nach den Projekten der Kollegen Fischer oder (!) Schneider. Bild 4: Die Suche nach den Mitarbeitern Fischer beziehungsweise Schneider Erst wenn Sie das genauer formulieren, fällt die vorherige Unmöglichkeit auf. Sie hätten nämlich alle Datensätze gesucht, deren Nachname Fischer und gleichzeitig Schneider lautet. Es gibt aber nur Datensätze, deren Nachname Fischer oder Schneider ist. Um mehrere Kriterien mit ODER zu verknüpfen, schreiben Sie diese in untereinander liegende Zeilen. Am linken Rand zeigt Access dort für die erste Folgezeile schon ein oder: an. Es sind aber beliebige weitere Zeilen für eine ODER-Verknüpfung nutzbar (s. Bild 4). Das Ergebnis liefert in diesem Fall je ein Projekt pro Mitarbeiter (s. Bild 5). Bild 5: Die Abfrage mit ODER-Verknüpfung arbeitet korrekt. Automatische Abfrage-Umgestaltung Sobald Sie eine Abfrage wie diese (unter dem Namen qryProjekteMitMitarbeiternFischerOderSchneider) zuerst gespeichert, geschlossen und dann wieder in der Entwurfsansicht geöffnet haben, sieht sie anders aus als zuvor (s. Bild 6). Die beiden Filterkriterien stehen nicht mehr untereinander, sondern in der gleichen Zelle nebeneinander und sind durch das Schlüsselwort ODER verbunden. Bild 6: Nach dem Speichern sieht der Abfrage-Entwurf anders aus. Damit haben wir auch die Lösung gefunden, wie mehrere Kriterien für eine einzige Spalte durch ein logisches UND verbunden werden können. Sie können ja mal testen, was passiert, wenn Sie die Suchbegriffe Fischer und Schneider tatsächlich mit UND verknüpfen (s. Bild 7). Wie schon vorausgesagt, gibt es keinen Datensatz, dessen Nachname beide Kriterien gleichzeitig erfüllt. Wofür es dieses logische UND dann gibt? Bild 7: So suchen Sie nach Fischer und Schneider gleichzeitig. Seite 3 Jedenfalls nicht für Textfelder, dort können Sie praktisch immer von einem logischen ODER ausgehen, aber in Zahlen- Access [basics] 8/2010 www.access-basics.de • Das Doppelkreuz oder Rautenzeichen (#) steht noch genauer für exakt eine Ziffer an dieser Stelle. Um nun also den so unscharf beschriebenen Nachnamen zu finden, können Sie darauf bauen, dass praktisch alle Berufsbezeichnungen auf „er“ enden. Erstellen Sie eine neue Abfrage basierend auf tblMitarbeiter und geben für das Feld Nachname als Kriterium *er ein. Access korrigiert das beim Verlassen der Zelle direkt in die vorgeschriebene Syntax Wie „*er“ (s. Bild 8). Und siehe da, es war kein Jäger, sondern wohl die Frau Förster, die gemeint war (s. Bild 9)! Bild 8: Diese Abfrage findet alle Nachnamen, die auf „er“ enden. Bild 9: Da haben wir alle Nachnamen, die so ähnlich wie ein Beruf klingen. Mit dem Sternchen, also der sehr unscharfen Suche, kommen wir aber nicht weiter, wenn sich jemand nur daran erinnert, dass irgendwie der vorletzte Buchstabe im Vornamen ein „i“ gewesen war. Eine Suche nach Wie „*i*“ würde nämlich auch Erich und Dirk nennen. Hier muss das Kriterium daher so lauten (s. Bild 10): Wie „*i?“ feldern wird es wichtig werden, wie Sie im Beitrag Abfragen für die Datenauswahl - Teil V: Filtern nach Zahlen sehen. Einsatz von Platzhaltern Bisher haben wir aber immer exakte Suchkriterien gehabt. Was ist, wenn Sie es nur ungefähr wissen? Der gesuchte Mitarbeiter hatte irgendwie so einen Nachnamen, der wie ein Beruf klingt: Jäger oder so... Auch das können Abfragen leisten, nämlich mit dem Wie-Operator und drei Joker-Zeichen. • Das Sternchen (*) erlaubt beliebig viele Zeichen an dieser Position, es kann aber eben auch gar keines dort sein. • Das Fragezeichen (?) verlangt, dass dort genau ein Zeichen stehen muss. Seite 4 Bild 10: Mit diesem Kriterium finden Sie alle Vornamen, deren vorletzter Buchstabe ein „i“ ist. Bild 11: David und Julia sind die einzigen Vornamen, die dieses Kriterium erfüllen Access [basics] 8/2010 www.access-basics.de Das bedeutet, dass vor dem „i“ beliebig viele Buchstaben stehen dürfen, danach aber genau ein Buchstabe stehen muss. So ermitteln Sie, dass nur noch David und Julia in Frage kommen, wie Bild 11 zeigt. Für ein Beispiel mit der Raute (#) braucht es vor allem einen Text, der auch Ziffern enthält. Tragen Sie also zuerst in der Tabelle tblKunden einen neuen Datensatz ein für die Firma pro9 in 52099 Aachen, Hauptstraße 1, mit Frau Julia Meier als Ansprechpartnerin. Auf dieser Tabelle basierend erstellen Sie anschließend eine neue Abfrage mit dem Feld Kunde und dem Kriterium Wie „*#*“. Dieses Kriterium verlangt, dass irgendwo im Kundennamen eine Ziffer vorkommt (s. Bild 12). Natürlich ist es keine wirklich große Überraschung, dass nun die frisch hinzugefügte Firma pro9 angezeigt wird, denn genau das sollte ja gelingen. Bild 12: So finden Sie einen Text mit einer Ziffer darin. Mit diesen Techniken können Sie sehr flexibel nach Texten filtern. Mit Zahlen werden wir uns im Artikel Abfragen für die Datenauswahl - Teil V: Filtern nach Zahlen beschäftigen und in einem Artikel in einer der kommenden Ausgaben geht es um das Filtern nach Datumswerten. Bild 13: Diese Firma enthält wie gesucht eine Ziffer im Namen. Impressum Access [basics] wird monatlich herausgegeben von: André Minhorst | Fachverlag für Softwareentwicklung | Borkhofer Straße 17 | 47137 Duisburg Die hier veröffentlichten Texte sind urheberrechtlich geschützt. Übersetzung und Vervielfältigung bedürfen der ausdrücklichen schriftlichen Genehmigung des Verlages. Sämtliche Veröffentlichungen in Access [basics] erfolgen ohne Berücksichtigung eines eventuellen Patentschutzes, auch werden Warennamen ohne Gewährleistung einer freien Verwendung benutzt. André Minhorst Fachverlag für Softwareentwicklung übernimmt für beschriebene oder zum Download bereitstehende Programme weder Gewähr noch Haftung, außer für Vorsatz oder grobe Fahrlässigkeit. Bezugspreise erfahren Sie auf www.access-basics.de. Redaktion: André Minhorst (V.i.S.d.P) | Telefon: 0203/4495577 | E-Mail: info@access-basics.de | Internet: www.access-basics.de Geschäftsführung, Herstellung, Text- und Schlussredaktion, Layout von Magazin und Webseite: André Minhorst Autoren: Lorenz Hölscher, André Minhorst ISSN: 2190-8761 Seite 5 Access [basics] 8/2010 www.access-basics.de Abfragen für die Datenauswahl Teil IV: Einsatz von Parametern Der Aufbau von Abfragen ist gar nicht so schwer – zumindest wenn Sie sich auf Abfragen mit einer überschaubaren Anzahl verknüpfter Abfragen beschränken. Allein eines nervt: Wenn Sie mal die Kriterien für die Auswahl der gesuchten Datensätze ändern möchten, müssen Sie jedesmal in den Abfrageentwurf wechseln, dort die gewünschten Einstellungen vornehmen und wieder zur Datenblattansicht zurückkehren. Damit dies etwas einfacher wird, stellt Ihnen dieser Artikel Abfrage-Parameter vor. Finden Sie es praktisch, durch ständige Anpassung der Kriterien nach den Mitarbeitern zu suchen? Wenn das so bliebe, müsste der Chef jedes Mal in die Entwurfsansicht wechseln, dort den Namen eintragen und erst dann diese Abfrage ausführen. Alternativ kommen viele AccessNutzer jetzt auf die Idee, für jeden Mitarbeiter eine Abfrage zu speichern, also qryXYZ_Fischer, qryXYZ_TabeaNeukirch, qryXYZ_ ErichNeukirch und so weiter. Bild 1: So geben Sie einen Parameter an. Parameter-Abfrage Das heißt, dass Sie für jeden Mitarbeiter eine eigene Abfrage anlegen müssten – und das für jede gewünschte Information. Vom Platzbedarf in der Access-Datenbank her ist das relativ unproblematisch. Abfragen brauchen nur wenig Speicher, denn sie speichern nur die Frage (Sie werden beim Stichwort SQL sehen, das es sich nur um einen schlichten Text handelt) und nicht die Antwort (die aus Millionen Datensätzen bestehen könnte). Manch ein Entwickler nutzt dies ohne Rücksicht aus und erstellt Abfragen nach diesem Muster: qryUmsatzJanuar, qryUmsatzFebruar bis qryUmsatzDezember. Aber wo bleibt da die Übersicht für Sie als Nutzer der Datenbank? Sie müssten mit zehn Mal so vielen Abfragen zurechtkommen und für jeden neuen Mitarbeiter außerdem eine neue Abfrage anlegen. Was Sie in diesem Artikel lernen »» Wozu brauche ich Parameter-Abfragen? »» Wie formulare ich eine Parameter-Abfrage? »» Wie kann ich mit einer Parameter-Abfrage viele gleichartige Abfragen ersetzen? Seite 6 Es gibt in Access einen besonderen Typ Auswahl-Abfragen, dessen Kriterien flexibel sind: die Parameter-Abfragen. Sie fragen bei jeder Ausführung immer nach, welches Kriterium dieses Mal berücksichtigt werden soll. Das ist genau für den Fall gedacht, dass Sie bei jedem neuen Aufruf der Abfrage die Daten zu einem anderen Mitarbeiter in Erfahrung bringen möchten. Bauen wir doch gleich eine erste Parameter-Abfrage. Dazu kopieren Sie die bereits erstellte Abfrage qryProjekteMitMitarbeiternFischerUndSchneider und geben der Kopie den Namen qryProjekteMitMit arbeiternParameter. Dies erledigen Sie am schnellsten, indem Sie qryProjekteMitMitarbeiternFischerUndSchneider im Datenbankfenster (Access 2003 und älter) beziehungsweise im Navigationsbereich (Access 2007 und jünger) markieren und nacheinander die Tastenkombinationen Strg + C und Strg + V betätigen. Access fragt dann nach dem Namen für die Kopie des Objekts. Öffnen Sie die neue Abfrage und löschen Sie alle bisherigen Kriterien. Dort, wo bisher der Text „Fi- Access [basics] 8/2010 www.access-basics.de scher“ Und „Schneider“ stand, fügen Sie nun einen sogenannten Parameter ein. Der Parameter muss in eckigen Klammern stehen und darf nicht den Namen eines der in den zugrunde liegenden Tabellen enthalten. Wenn Sie nun beispielsweise Bild 2: Eingabe des Parameters als Kriterium der Abfrage [Welcher Nachname?] als Kriterium eingeben, sind Sie auf der sicheren Seite (s. Bild 1): So heißt garantiert keines der Felder, zumindest, wenn Sie sich sich an die gängigen Konventionen für die Benennung von Feldern halten (siehe [basics] Konventionen). Warum aber formulieren wir den Namen des Parameters als Frage? Nun: Access hat einen eingebauten Mechanismus, der alle Elemente einer Abfrage, die nicht als Feldname oder Schlüsselwort erkannt werden, entdeckt und den Benutzer fragt: „Diesen Wert kenne ich nicht! Sag mir, welchen Wert ich dafür annehmen soll!“ Sobald ein Benutzer diese Abfrage ausführt, erscheint ein kleiner Dialog mit dem Feldnamen darüber und einem Eingabefeld, damit der Benutzer den Inhalt des Kriteriums angeben kann. Geben Sie nun Fischer ein und bestätigen den Dialog (s. Bild 2), so sehen Sie ein ganz normales Abfrage-Ergebnis in der Datenblattansicht (s. Bild 3). Seite 7 Bild 3: Die Parameter-Abfrage hat funktioniert und zeigt die gefilterten Datensätze für Fischer an. Sobald Sie wieder in den Entwurf der Abfrage zurückwechseln, ist der Parameter-Inhalt vergessen. Sie müssen ihn daher bei jeder Ausführung erneut eingeben. Damit haben wir uns erst einmal ganz viele ähnliche Abfragen erspart. Eine einzige Parameter-Abfrage ermöglicht die wechselnde Eingabe von Kriterien zur Ausführungszeit. Der Chef muss also nicht mehr in den Abfrage-Entwürfen herumbasteln, sondern braucht diese einfach nur noch zu öffnen. Access [basics] 8/2010 www.access-basics.de Abfragen für die Datenauswahl Teil V: Filtern nach Zahlen Viele Auswertungen basieren auf Zahlenkriterien. Welche Artikel kosten mehr als 100,- Euro? Welcher Kunde hat den größten Umsatz generiert? Welcher Artikelbestand erfordert eine Nachbestellung? Aber auch: Welche Bestellungen gehören zum Kunden mit der Kundennummer 1234? Dieser Artikel zeigt, wie Sie Daten mit Zahlenkriterien filtern. Bild 1: Abfrage aller Datensätze, deren Lagerbestand den Wert 0 hat Da die Projektzeiterfassung als eigentliche Beispieldatenbank von Access [basics] zum aktuellen Zeitpunkt keine umfassenden Zahlenfelder bietet, die wir nach Herzenslust für das Durchführen von Beispielen heranziehen können, weichen wir im Rahmen dieses Artikels auf die Südsturm-Datenbank aus. Südsturm-Datenbank? War da nicht was? Gibt es da nicht eine Beispieldatenbank von Microsoft namens Nordwind? Genau: Und eben dieser haben wir die Tabellen entnommen. Bild 2: Ergebnis der Abfrage aller ausverkauften Datensätze Und nicht nur das: Wir haben auch noch einige Anpassungen durchgeführt, damit die Tabellen- und Feldnamen den unter [basics] Konventionen beschriebenen Regeln entsprechen. Doch zur Sache: Wir beginnen mit der Tabelle tblArtikel, die einige interessante Zahlenfelder enthält. Da wäre zum Beispiel der Einzelpreis, der Bestand, die bestellten Einheiten oder der Lagerbestand. Feld mit bestimmtem Zahlenwert Die einfachsten Abfragen mit Zahlenkriterien enthalten nur den Vergleichswert und liefern so alle Datensätze, bei denen etwa das Feld Lagerbestand den Wert 0 aufweist. Dazu legen Sie eine neue Abfrage mit der Tabelle tblArtikel an und fügen die Felder ArtikelID, Artikelname und Lagerbestand zum Entwurfsraster hinzu (siehe [basics] Abfrage erstellen) und speichern diese unter dem Namen qryLagerbestandIstNull. Tragen Sie in der Zeile Kriterien den Wert 0 für das Feld Lagerbestand ein (siehe Bild 1). Das Ergebnis liefert genau die gewünschten Datensätze (siehe Bild 2). Nur Zahlen! Wenn Sie Vergleichswerte für Zahlenfelder angeben, dürfen Sie ebenfalls nur Zahlen verwenden (abgesehen von den nachfolgend vorgestellten Operatoren). Sie dürfen keine Texte wie beispielsweise Euro oder % zusätzlich zum Zahlenwert eingeben – Access meldet dann einen Fehler. Größer, kleiner, gleich Dieses Kriterium entspricht prinzipiell dem Ausdruck =0. Diesen können Sie alternativ eingeben. Von dort aus kommen wir schnell auf die übrigen möglichen Operatoren für das Filtern von Zahlenwerten: • Größer (>) • Größer gleich (>=) • Kleiner (<) Was Sie in diesem Artikel lernen »» Einsatz von Kriterien für Zahlenfelder »» Verwendung von Operatoren wie AND, OR, BETWEEN oder IN »» Kombination mehrerer Kriterien in Abfragen »» Einsatz des Schlüsselworts NULL Seite 8 • Kleiner gleich (<=) Statt =0 können Sie also beispielsweise Werte wie >0, >=10 oder <100 eingeben. Access [basics] 8/2010 www.access-basics.de Ungleich Für das Finden von Datensätzen, deren Feldwert ungleich dem betreffenden Wert ist, haben Sie zwei Möglichkeiten: Sie können erstens die Kombination aus größer (>) und kleiner (<) verwenden, also <>, aber auch den Operator NOT einsetzen. Dieser ist übrigens mit beliebigen Ausdrücken kombinierbar. Wenn Sie alle Artikel finden möchten, deren Lagerbestand ungleich 10 ist, verwenden Sie also entweder das Kriterium <>10 oder NOT 10. Lassen Sie sich nicht täuschen, wenn Sie NOT 10 als Kriterium eintragen und Access dies gleich nach dem Verlassen des Feldes in Nicht 10 umwandelt (siehe Bild 4): Dies ist eine Eigenschaft der deutschsprachigen AccessVersion. Im Hintergrund speichert Access ohnehin den SQLAusdruck, und zwar mit englischen Schlüsselwörtern. Zum Thema SQL kommen wir zu einem späteren Zeitpunkt. Zwischen zwei Werten Bild 3: Schlüsselwörter wie NOT werden im Abfrageentwurf der deutschen Access-Version eingedeutscht. Bild 4: Abfragen eines Bereiches von Werten Bild 5: Bereichsabfrage, diesmal mit BETWEEN Vielleicht möchten Sie auch einmal alle Artikel ermitteln, deren Preis zwischen 10,- Euro und 20,- Euro liegt. Dann haben Sie ebenfalls zwei Möglichkeiten: • Sie verwenden den AND-Operator, um die Kriterien >=10 und <=20 zu verknüpfen. Der Abfrage-Editor macht dann >=10 Und <=20 daraus (s. Bild 5). • Oder Sie verwenden direkt den für solche Fälle vorgesehen BETWEEN ... AND ...-Operator. Dazu geben Sie den Ausdruck BETWEEN 10 AND 20 als Kriterium, was umgehend in Zwischen 10 Und 20 umgewandelt wird (s. Bild 6). Bild 6: Artikel, die weniger als 10,- Euro oder mehr als 20,Euro kosten Englische und deutsche Operatoren Warum führen wir immer die englischen Schlüsselwörter an, wenn der Abfrage-Editor diese doch in die deutschen Pendants umwandelt – zumindest in der deutschen Version von Access? Ganz einfach: Access speichert die Ausdrücke intern in der englischen Version. Und das ist auch die Version, die Sie später kennenlernen, wenn Sie einmal manuell Abfrageausdrücke zusammenstellen. Das kommt zwar kaum vor, weil Sie immer den Abfrage-Designer zu Hilfe nehmen können, aber es gibt auch Ausnahmen. Seite 9 Werte außerhalb eines Bereichs Vielleicht möchten Sie auch einmal alle Artikel auflisten, die gerade nicht im Preissegment zwischen 10,- Euro und 20,- Euro liegen. Dann hilft der ANDOperator nicht weiter: Er liefert nur Datensätze, für die alle mit Und verknüpften Kriterien gelten. Hier kommt der OR-Operator ins Spiel. Der Ausdruck <10 Access [basics] 8/2010 www.access-basics.de Or >20 (zuverlässig umgewandelt in <10 Oder >20) liefert alle Artikel, deren Preis nicht zwischen 10,Euro und 20,- Euro liegt (s. Bild 7). Bild 7: Aufteilung der per OR verknüpften Kriterien auf zwei Zeilen Auch hier gibt es alternative Formulierungen des Kriteriums, zum Beispiel: NOT BETWEEN 10 AND 20 Nach dem gleichen Schema sollte sich der Ausdruck >10 AND <20 negieren lassen: Nicht >10 Und <20 Tut er aber nicht: Dieser Ausdruck liefert lediglich die Datensätze, deren Preis kleiner gleich 10,- Euro ist. Warum? Weil der AND-Operator nicht zuerst >10 und <20 verkettet und dann den gesamten Ausdruck mit NOT negiert, sondern weil er Nicht >10 und <20 zusammenfasst. Unser Plan geht erst auf, wenn wir >10 AND <20 in Klammern einfassen und diesen Ausdruck mit NOT negieren: Bild 8: Abfrage von Artikeln nach bestimmten Kategorien NOT (>10 Und <20) Für den ursprünglichen Ansatz des Kriteriums <10 OR >20 gibt es noch eine alternative Schreibweise im Abfrage-Editor. Dabei verwenden Sie die oft vernachlässigte oder-Zeile unterhalb der Kriterien-Zeile (siehe Bild 1). Beim Schließen wandelt Access dies jedoch wieder in <10 OR >20 um. Bild 9: Abfrage von Artikeln nach bestimmten Kategorien per KategorieID Mit Mengen vergleichen Wenn Sie ein Feld nach einer ganzen Reihe von Kriterien filtern möchten, würden Sie normalerweise entsprechend viele durch OR verknüpfte Vergleichswerte angeben. Wenn Sie beispielsweise alle Artikel erhalten möchten, die zu den Kategorien Getränke, Fleischprodukte oder Naturprodukte gehören, führen Sie die beiden Tabellen tblArtikel und tblKategorien wie in Bild 8 in einer Abfrage zusammen und verwenden das Feld Kategoriename als Kriterienfeld. Der Vergleichswert heißt dann beispielsweise: "Getränke" Oder "Fleischprodukte" Oder "Naturprodukte" Alternativ zu dieser Schreibweise verwenden Sie das IN-Schlüsselwort, das eine in Klammern angegebene Liste der mit OR verknüpften Vergleichswerte erwartet: IN ("Getränke";"Fleischprodukte";"Naturprodukte") Seite 10 Damit sind wir jedoch ein wenig vom Thema abgekommen, denn eigentlich behandeln wir hier ja die Filtermöglichkeiten für Zahlenfelder. Also schnell zurück: Eine Kategorie ist für einen Artikeldatensatz nicht mehr als eine Zahl im Fremdschlüsselfeld KategorieID. Eine Abfrage wie oben, die Artikel nach der Bezeichnung der Kategorie auswählt, werden Sie in der freien Wildbahn hoffentlich nicht finden. Dort können Sie vielleicht Kategorien aus einem Steuerelement wie einem Listenfeld auswählen und sich die entsprechenden Artikel anzeigen lassen. Dabei wird die Abfrage jedoch nicht auf den Kategorienamen, sondern auf die im Listenfeld ausgeblendeten KategorieID zugreifen. Tun wir also so, als ob wir gerade von einem solchen Listenfeld die Anfrage nach allen Artikeln der Kategorien 1, 6 und 7 erhal- Access [basics] 8/2010 www.access-basics.de ten hätten. Die Abfrage benötigt die Tabelle tblKategorien nicht mehr, denn die KategorieID ist ja bereits in der Artikeltabelle enthalten. In Entwurf sieht die Abfrage nun wie in Bild 3 aus und liefert genau die gewünschten Datensätze. IN-Operator mit Abfrageergebnis Der Clou beim IN-Operator ist, dass die Werte auch aus einer weiteren Abfrage stammen können. Dummerweise ist bereits hier der Zeitpunkt gekommen, zu dem Sie nicht mehr ohne eine in SQL formulierte Abfrage herumkommen. Bild 10: Abfrage von Artikeln nach bestimmten Kategorien per KategorieID Wenn Sie die Werte für den IN-Operator über eine Abfrage erhalten möchten, müssen Sie diese in reinem SQL angeben. Dennoch erledigen wir die Vorarbeit mit Hilfe des Abfrage-Assistenten. Dazu erstellen Sie eine neue Abfrage auf Basis der Tabelle tblKategorien, welche die KategorieID für drei Kategorien Getränke, Fleischprodukte und Naturprodukte liefert (siehe Bild 10). Bild 11: SQL-Abfrage als Parameter der IN-Klausel Die als Parameter der IN-Klausel angegebene Abfrage muss eine Bedingung unbedingt erfüllen: Sie darf nur ein einziges Feld zurückgeben. Wenn Sie nur das Feld KategorieID der Abfrage qryBestimmteKategorien erhalten möchten, verwenden Sie den folgenden SQL-Ausdruck: SELECT KategorieID FROM qryBestimmteKategorien Diese Abfrage liefert schlicht und einfach die drei Werte 1, 6 und 7 als Ergebnis. Und genau das benötigen wir für die IN-Klausel. Also fügen wir den SQLAusdruck dort in die Klammern ein und erhalten einen Abfrage-Entwurf wie in Bild 11. Ein interessantes Beispiel für den IN-Operator in Kombination mit einer sogenannten Unterabfrage finden Sie im Artikel Formulare für die Datenbearbeitung – Teil V: m:n-Beziehungen in Listenfeldern darstellen. Literale und Ausdrücke Bislang haben wir nur feste Zahlen als Vergleichsoperatoren verwendet – mit Ausnahme der Unterabfrage, welche die Werte für den IN-Operator lieferte. Es gibt jedoch noch viel mehr Möglichkeiten, Zahlenfelder mit Vergleichskriterien zu versehen. Seite 11 Bild 12: Vergleich eines Zahlenfeldes mit dem Inhalt eines anderen Feldes Bild 13: Alle Artikel, deren Lagerbestand kleiner oder gleich dem Mindestbestand ist In der Tabelle tblArtikel finden Sie zum Beispiel die Felder Lagerbestand und Mindestbestand. Wir möchten zunächst alle Artikel ermitteln, bei denen der Lagerbestand kleiner oder gleich dem Mindestbestand ist. Dazu fügen Sie dem Feld Lagerbestand das Kriterium <=[Mindestbestand] hinzu (s. Bild 12). Access [basics] Sie müssen den Feldnamen des Vergleichskriteriums unbedingt in eckige Klammern setzen, da Access diesen sonst in ein Zeichenketten-Literal umwandelt (also "Mindestbestand") – und das führt zu einem Fehler. Genaugenommen wäre es sogar sinnvoll, den Tabellen voranzustellen, also [tblArtikel].[Mindestbestand]. Auf diese Weise schließen Sie bei Abfragen auf Basis mehrerer Tabellen aus, dass eventuell mehrfach vorkommende Felder nicht eindeutig referenziert werden. Ansonsten läuft die Abfrage reibungslos, wie Bild 13 zeigt. 8/2010 www.access-basics.de Bild 14: Tatsächlich zu bestellende Artikel Allerdings enthält die Tabelle bereits ein Feld namens BestellteEinheiten und Auslaufartikel – der Korrektheit halber sollten Sie also noch das Feld BestellteEinheiten auf den Wert 0 und Auslaufartikel auf den Wert False prüfen, bevor Sie neue Artikel bestellen. Dies ist ein willkommener Anlass, den impliziten AND-Operator des Abfrage-Editors zu erwähnen. Zur obigen Abfrage fügen Sie nun noch die beiden Felder BestellteEinheiten und Auslaufartikel hinzu. Es sollen nur die Artikel tatsächlich bestellt werden, die noch nicht bestellt wurden (BestellteEinheiten = 0) und die nicht als Auslaufartikel gekennzeichnet sind (Auflaufartikel = False). Zusammen mit dem Vergleich von Lagerbestand und Mindestbestand haben wir nun drei Kriterien, die im Abfrageentwurf zwar in verschiedenen Spalten, aber alle in der gleichen Zeile landen (siehe Bild 14). Dies bedeutet automatisch, dass diese Kriterien mit dem AND-Operator verknüpft werden! Unser Kriterium heißt also eigentlich: Bild 15: Suche nach leeren Feldern Entfernen Sie zu Testzwecken beispielsweise einmal den Inhalt des Feldes Einzelpreis eines Datensatzes der Tabelle tblArtikel. Ein Artikeldatensatz ohne Preis führt früher oder später zu Problemen – wir wollen solche Datensätze daher schnell identifizieren. Wie aber gestalten wir das Kriterium für diesen Zweck? Folgendes funktioniert nicht: • 0 (das Feld enthält nichts, also auch nicht den Wert 0) • leere Zeichenfolge, also "", liefert gar einen Fehler, weil ein Zeichenkettenliteral ein unzulässiger Vergleichswert für ein Zahlenfeld ist Was wir benötigen, ist ein ganz spezieller Ausdruck, nämlich NULL. Diesen wandelt Access umgehend in Ist Null um, wie Bild 15 zeigt. Dieser Ausdruck liefert definitiv nur solche Datensätze, die keinen Wert für das entsprechende Feld enthalten. Lagerbestand<=Mindestbestand AND BestellteEinheiten=0 AND Auslaufartikel=False NULL-Werte ermitteln Wenn wir mit Zahlenfeldern arbeiten, sind wir nicht vor leeren Feldern gefeit (außer natürlich, Sie legen im Tabellenentwurf fest, dass ein Feld nicht leer sein darf). Seite 12 Access [basics] 8/2010 www.access-basics.de VBA-Programmierung Teil II: Variablen in VBA Eine der wichtigsten Techniken in Programmiersprachen besteht darin, dass Sie sich veränderliche Werte zur Laufzeit merken können. Dazu dienen Variablen, in denen der Inhalt vorübergehend gespeichert wird. VBA bietet die Möglichkeit zum Speichern von Werten wie beispielsweise von Berechnungsergebnissen in sogenannten Variablen. Eine Variable ist im Prinzip ein Aufbewahrungsort für einen Wert. Bevor Sie eine Variable in einer VBA-Routine verwenden, sollten Sie diese einen aussagekräftigen Namen geben. Im gleichen Zuge legen Sie die Art der darin gespeicherten Daten fest. Es gibt beispielsweise Variablentypen zum Speichern von Text, Zahlen oder Datumsangaben. Das Benennen und das Zuweisen des Datentyps nennt sich Deklarieren und sieht beispielsweise so aus: Wenn Sie die drei Zeilen in eine Sub-Prozedur einfügen und diese ausführen ([basics] VBA-Prozedur ausführen), erscheint im Direktfenster der Wert 100. Dim intZahl As Integer Debug.Print ingZahl Diese Anweisung deklariert eine Variable namens intZahl und weist ihr den Datentyp Integer zu. Der Variablenname setzt sich aus einem Präfix (int) und der Beschreibung des Inhalts (Zahl) zusammen. Diese Zeile greift schlicht auf eine zuvor nicht gefüllte Variable zu. Solche Fehler vermeiden Sie auf einfache Weise. Schreiben Sie einfach die folgende Zeile in den Modulkopf: Durch den Einsatz des Präfix können Sie beim Programmieren gleich erkennen, welchen Datentyp eine Variable besitzt und brauchen nicht erst die Deklarationszeile zu konsultieren. Option Explicit Hinter dem Präfix steht eine kurze Beschreibung des Werts, den die Variable enthalten soll (hier schlicht Zahl). Die Regeln zur Benennung von Variablen haben wir unter [basics] Konventionen zusammengefasst. Variablendeklaration erzwingen Wenn Sie Variablen auf die oben beschriebene Art deklarieren, könnten einfache Tippfehler dazu führen, dass die Routine nicht mehr wie erwartet funktioniert. Wenn die dritte Zeile beispielsweise wie folgt lautet, gibt die Prozedur gar keinen Wert mehr im Direktfenste raus: Bild 1: Ausgabe des in einer Variablen gespeicherten Wertes im Direktfenster Wenn Sie eine Variable deklariert haben, können Sie diese Werte zuweisen und über die Variable auf den gespeicherten Wert zugreifen: Dim intZahl As Integer intZahl = 100 Debug.Print intZahl Was Sie in diesem Artikel lernen »» Welche Basisdatentypen stellt VBA bereit? »» Wie speichere ich einen Wert in einer Variablen und rufen ihn wieder ab? »» Welchen Wertebereich haben die verschiedenen Datentypen? »» Was ist der Unterschied zwischen Gleit- und Festkommazahlen? Seite 13 Bild 2: Fehlermeldung beim Verwenden einer nicht deklarierten Variablen Access [basics] 8/2010 www.access-basics.de Der VBA-Editor prüft nun vor dem Ausführen der Routine, ob alle verwendeten Variablen auch deklariert wurden. Dies ist bei ingZahl nicht der Fall (deklariert wurde ja nur intZahl). Der VBA-Editor liefert daraufhin eine entsprechende Fehlermeldung und markiert die betroffene Zeile (s. Bild 2). Diese Option gilt nicht anwendungsweit, sondern nur in den Modulen, in denen diese enthalten ist. Wenn die Option nicht in allen Modulendes VBA-Projekts einer Access-Anwendung aktiv ist, sollten Sie dies manuell nachholen. Für neue Module können Sie dies automatisieren: Dazu stellen Sie einfach eine Option des VBA-Editors ein. Klicken Sie im VBA-Editor auf den Menüeintrag Extras|Optionen und aktivieren Sie im nun erscheinenden Dialog die Option Variablendeklaration erforderlich (s. Bild 3). Fortan wird die Zeile Option Explicit automatisch in alle neuen Module integriert. Deklaration mehrere Variablen in einer Zeile Aus Gründen der Übersicht sollten Sie jede Variable in einer eigenen Zeile deklarieren, also zum Beispiel so: Bild 3: Aktivieren des Kontrollkästchens Variablendeklaration erforderlich Warum gibt es mehrere Datenzahlentypen? Nun: Es gibt unterschiedlich große Zahlen mit und ohne Nachkommastellen. VBA stellt für die verschiedenen Fälle unterschiedliche Datentypen bereit. Ganzzahlen Bei den Zahlen gibt es zunächst die folgenden Ganzzahlentypen: • Byte: Ganzzahlen von 0 bis 255 (ein Byte Speicherbedarf) • Integer: Ganzzahlen von -32.768 bis 32.767 (zwei Bytes Speicherbedarf) Dim i As Integer Dim strVorname As String Dim datStart as Date Sie können allerdings auch mehrere Deklarationen in eine Zeile schreiben: Dim i As Integer, j As Integer Beachten Sie, dass Sie zu jeder Variablen den Datentyp angeben müssen – anderenfalls nimmt VBA automatisch den Variablentyp Variant an. • Long: Ganzzahlen von -2.147.483.648 bis 2.147.483.647 (vier Bytes Speicherbedarf) Achtung: Wenn Sie einer Ganzzahl-Variablen eine Zahl mit Nachkommastellen zuweisen, werden diese kommentarlos abgeschnitten. Wenn Sie einer Zahl-Variablen einen Wert zuweisen, der außerhalt des angegebenen Wertebereichs liegt, löst dies einen Fehler aus. Das wäre zum Beispiel hier der Fall – der Wertebereich für den Datentyp Integer reicht nur bis 32.767: Dies ist zwar nicht unbedingt fehlerhaft, aber Variablen dieses Datentyps nimmt den meisten Speicherplatz ein (mehr dazu weiter unten). Dim intZuGross As Integer intZuGross = 32768 Basisdatentypen Gleitkommazahlen Die Programmiersprache VBA kennt einige Basisdatentypen. Die meisten davon speichern Zahlen, aber Sie können auch Zeichenketten in Variablen zwischenlagern. Daneben gibt es zwei Gleitkommazahlentypen: Seite 14 • Single: Gleitkommazahlen von -3,402823E38 bis -1,401298E-45 für negative Werte und 1,401298E- Access [basics] 8/2010 www.access-basics.de 45 bis 3,402823E38 für positive Werte (vier Bytes Speicherbedarf) • Double: Gleitkommazahlen von -1,79769313486232E308 bis 4,94065645841247E324 für negative Werte und 4,94065645841247E324 bis 1,79769313486232E308 für positive Werte (acht Bytes Speicherbedarf) Festkommazahlen Neben den Gleitkommazahlen gibt es auch einen Festkommazahlentyp: • Currency: Festkommazahlen im Bereich von -922.337.203.685.477,5808 bis 922.337.203.685.477,5807 (acht Bytes Speicherbedarf) Der Unterschied zwischen Ganzzahlen auf der einen und Gleitkomma- und Festkommazahlen auf der anderen Seite sollte klar sein, aber was unterscheiden Gleitkomme- und Festkommazahlen voneinander? Für den Einstieg reicht es zu wissen, dass Gleitkommazahlen grundsätzlich ungenau sind. Dies belegt das folgende Beispiel: Dim sng As Single Dim i As Integer For i = 1 To 30 sng = sng + 0.1 Debug.Print sng Next i ten. Die Bezeichnung Currency ist aber insofern irreführend, weil Sie natürlich beliebige Werte darin speichern können – auch für Prozentzahlen bietet sich dieser Datentyp an. Datum Im Prinzip ebenfalls ein Zahlentyp ist Date. Es enthält immer ein Datum und eine Uhrzeit. Dabei speichert dieser Datentyp Zahlenwerte, die dem Wertebereich des Datentyps Double entsprechen. Wir kommen später auf die genaue Speicherung von Werten in Date-Variablen zu sprechen. True/False Auch der Datentyp Boolean speichert Zahlenwerte, prinzipiell von -32.768 bis 32.766. Dabei entspricht -1 dem Wert True und 0 dem Wert False (auch alle übrigen Werte entsprechen False). Zeichenketten Variablen des Datentyps String verwenden Sie zum Speichern beliebiger Texte. Die maximale Länge der gespeicherten Zeichenkette hängt vom System ab und durften auf unserem Testsystem beispielsweise bis zu 326.500.333 Zeichen lang sein. Mädchen für alles? Im Direktfenster steht beispielsweise nach dem achten Durchlauf der Schleife der Wert 0,8000001. Wenn Sie hier mit dem Festkommadatentyp Currency arbeiteten, erhielten Sie jeweils das richtige Ergebnis. Wenn Sie keinen Datentyp angeben, verwendet VBA automatisch den Datentyp Variant. Dieser kann alle möglichen Werte aufnehmen, also zum Beispiel Zahlen oder Text. Auf die Besonderheiten gehen wir in einem späteren Artikel ein. Objektvariablen Intern werden Nachkommastellen bei Gleitkommazahlen als Summe von Brüchen mit Zweierpotenzen dargestellt, 0,75 entspricht also beispielsweise 1/2 + 1/4. Eine Zahl wie 0,2 lässt sich so nie genau abbilden. Festkommazahlen können wegen der maximalen Anzahl von vier Nachkommastellen intern einfach mit dem Faktor 10.000 und somit als Ganzzahlen gespeichert werden. Der Wert 0,2 wird also intern einfach als 0,2 x 10.000 = 2.000 gespeichert. Ein Datentyp unterscheidet sich wesentlich von den übrigen: Object nimmt nicht den eigentlichen Inhalt auf, sondern nur einen Verweis auf den Ort im Speicher, an dem sich der Inhalt befindet. Currency als Festkommazahl eignet sich prima für Operationen im Finanzbereich, weil die durch Gleitkommazahlen verursachten Ungenauigkeiten sich dort schnell unangenehm bemerkbar machen könn- Speicherplatz von Variablen Seite 15 Bei einem solchen Objekt kann es sich beispielsweise um die aktuelle Datenbank, ein Recordset oder auch um ein Formular handeln. Wir gehen in einem weiteren Artikel genauer auf dieses Thema ein. VBA reserviert Speicherplatz für Variablen. Dies können Sie einfach nachvollziehen, indem Sie in einer Access [basics] 8/2010 www.access-basics.de Prozedur beispielsweise eine große Menge DoubleVariablen deklarieren (zum Beispiel 1.000): Sie können auch gleich Berechnungsergebnisse zuweisen: Public Sub Speicherplatz Dim dbl1 As Double Dim dbl2 As Double Dim dbl3 As Double ... Dim dbl1000 As Double Stop End Sub lngBeispiel = 1 + 2 Oder Sie verwenden den Wert einer Variablen in der Zuweisung zu einer weiteren Variablen: lngBeispiel1 = lngBeispiel + 3 Wenn Sie nun den Task Manager von Windows öffnen (Strg + Alt + Entf) und dort zur Registerseite Prozesse wechseln, finden Sie dort auch einen Eintrag namens MSACCESS.EXE mit einem Wert in der Spalte Arbeitsspeicher. Merken Sie sich den aktuellen Wert und starten Sie die obige Prozedur. Diese hält in der Stop-Zeile an. Der Speicherbedarf im Task Manager wächst nun sichtlich, was teilweise auch durch die Bereitstellung von Speicherplatz für die Variablen geschieht. Die hier verwendete Double-Variable reserviert beispielsweise acht Bytes. Sie können einer Variablen auch ihren eigenen Wert zuweisen, zum Beispiel um den Wert innerhalb einer Schleife schrittweise bis zum Wert 10 um 1 zu vergrößern: Dim intSumme As Integer Dim i As Integer For i = 1 To 10 intSumme = intSumme + 1 Next i Zeichenketten geben Sie prinzipiell genauso ein, allerdings müssen Sie die Zeichenkette in Anführungszeichen einfassen: strVorname = "Andre" Bei Verwendung von String-Variablen reserviert VBA zehn Bytes plus zwei Byte pro Zeichen. Auch das können Sie leicht im Taskmanager beobachten, indem Sie folgende Zeilen in einer Prozedur aufrufen: Dim str As String str = Space(2^24) Debug.Print Len(str) Die Space-Funktion weist der String-Variablen 224 Leerzeichen zu, was sich in einem Zuwachs von rund 32 Megabytes Speicherbedarf der Access-Anwendung manifestiert. Typische Fehler Fehler bei der Zuweisung von Variablenwerten entstehen, wenn Sie Werte eingeben, die den Wertebereich überschreiben oder wenn Sie einer Variablen einen anderweitig ungültigen Wert zuweisen – beispielsweise wenn eine Integer-Variable eine Zeichenkette aufnehmen soll: Dim intZahl As Integer intZahl = "Zahl" Dies löst eine Fehlermeldung aus. Variablen einsetzen Zusammenfassung und Ausblick Bei allen Datentypen außer Object weisen Sie den Wert einfach mit dem Gleichzeitszeichen zu. Bei Zahlendatentypen sieht das wie folgt aus: lngBeispiel = 1000 dblBeispiel = 12.3456 curBeispiel = 12.3456 Beachten Sie: Als Dezimaltrennzeichen wird das in der Systemsteuerung angegebene Zeichen eingesetzt, in Deutschland üblicherweise der Punkt. Seite 16 Sie haben nun die Basisdatentypen von Access kennengelernt. Einen wichtigen, nämlich Object, haben wir weitgehend ignoriert – ihn behandeln wir später in Zusammenhang mit den übrigen Datentypen. Access [basics] 8/2010 www.access-basics.de Formulare für die Datenbearbeitung Teil V: m:n-Beziehungen in Listenfeldern darstellen m:n-Beziehungen kann man auf verschiedene Arten abbilden. Die Variante mit Haupt- und Unterformular haben Sie bereits kennengelernt. In der Variante dieses Artikels kommen zwei Listenfelder zum Einsatz. Das erste enthält alle Datensätze, die mit dem im Formular angezeigten Datensatz verknüpft sind und das zweite alle übrigen. Das Hinzufügen und Entfernen verknüpfter Datensätze erledigen wir elegant per Schaltfläche oder per Doppelklick. Das Beispiel zu diesem Artikel soll mal wieder unsere Projektzeiterfassung in den Mittelpunkt stellen. Wenn mal ein Kunde anruft und eine Frage zu einem speziellen Thema hat, soll er bei der Firma Schwuppdiwupp gleich mit einem kompetenten Kollegen verbunden werden. Dummerweise weiß aber nicht jeder, welcher Mitarbeiter sich mit welchen Themen beschäftigt. Was liegt also näher, als in der Datenbank Informationen über die Fähigkeiten (neudeutsch Skills) der einzelnen Mitarbeiter zusammeln? Im Endergebnis soll dies wie in Bild 1 aussehen. Ein Doppelklick auf einen Eintrag im linken Listenfeld soll diesen ins rechte Listenfeld verschieben und umgekehrt. Mit den Schaltflächen mit dem Größer- und dem Kleiner-Zeichen lässt sich der jeweils markierte Eintrag verschieben. Die doppelten Größer/KleinerZeichen verschieben gleich alle Einträge zum jeweils anderen Listenfeld. Bild 1: Hinzufügen und Entfernen der Skills eines Mitarbeiters Datenmodell Für dieses Beispiel benötigen Sie zwei neue Tabellen. Die erste heißt tblSkills und speichert die einzelnen Skills. Sie enthält ein Primärschlüsselfeld namens SkillID und ein Textfeld namens Skill zum Eintragen der Skills. Bild 2: Mitarbeiter und ihre Skills im Datenmodell Die Zuordnung eines Skills zu einem Mitarbeiter erfolgt über die m:n-Verknüpfungstabelle tblMitarbeiterSkills. Die Tabelle enthält das Primärschlüsselfeld MitarbeiterSkillID sowie die beiden Felder MitarbeiterID und SkillID. Die beiden Felder sind mit den jeweiliWas Sie in diesem Artikel lernen »» m:n-Beziehungen mit Listenfeldern verwalten »» Daten zu einer m:n-Beziehung hinzufügen »» Daten einer m:n-Beziehung löschen »» Listenfelder mit Inhalten füllen Bild 3: Festlegen eines zusammengesetzten eindeutigen Schlüssels für die Tabelle tblMitarbeiterSkills Seite 17 Access [basics] 8/2010 www.access-basics.de gen Primärschlüsselfeldern der Tabellen tblMitarbeiter und tblSkills verknüpft (s. Bild 2). Damit die Benutzer der Datenbank keinem Mitarbeiter den gleichen Skill zweimal zuweisen können, legen Sie einen zusammengesetzten eindeutigen Index für die beiden Felder MitarbeiterID und SkillID der Tabelle tblMitarbeiterSkills fest (s. Bild 3). Wenn Sie die Tabellen angelegt haben, Bild 4: Zuordnen von Skills zu Mitarbeitern per Verknüpfungstabelle geben Sie wie in Bild 4 ein paar Datensätze in die Tabelle tblSkills und tblMitarbeiterSkills ein – dies erleichtert die Entwicklung auf Basis des Formulars mit den beiden Listenfeldern. Basisformular Das Formular bauen Sie wie in [basics] Gebundenes Formular anlegen beschrieben auf. Dabei verwenden Sie die Tabelle tblMitarbeiter als Datenherkunft und ziehen die wichtigsten Felder, hier beispielsweise die MitarbeiterID, AnredeID, Vorname und Nachname in den Detailbereich. Speichern Sie das Formular unter dem Namen frmMitarbeiterSkills. Damit ist die m-Seite der Beziehung bereits erledigt, aber das ist bei weitem nicht der aufwendigste Teil der Aufgabe. Nun folgen noch die beiden Listenfelder, die Schaltflächen zum Hin- und Herschieben der Skills zwischen den Listenfeldern und die notwendigen VBA-Routinen. Fügen Sie also zunächst zwei Listenfelder zum Formular hinzu und nennen Sie diese lstZugeordnet und lstNichtZugeordnet. Dazwischen legen Sie gleich vier Schaltflächen an, die folgende Namen erhalten: cmdEinzelnHinzufuegen, cmdAlleHinzufuegen, cmdAlleEntfernen und cmdEinzelnEntfernen. Als Beschriftung verwenden Sie der Einfachheit halber entsprechende Größer/Kleiner-Zeichen (<, <<, >> und >). Wie Bild 5 zeigt, ist das Formular nun bereits mit allen notwendigen Steuerelementen ausgestattet. Listenfeld mit zugeordneten Skills Nun weisen Sie dem Listenfeld der für den aktuellen Mitarbeiter ausgewählten Skills eine entsprechende Datensatzherkunft zu. Die Besonderheit dieser Abfrage ist, dass sie einen Wert des aktuell im Formular Seite 18 Bild 5: Entwurfsansicht des Formulars mit allen Steuerelementen angezeigten Datensatzes als Kriterium verwenden soll. Das Listenfeld soll schließlich nur die Skills für den aktuell im Formular sichtbaren Mitarbeiter anzeigen. Wie muss die Datenherkunft nun aussehen? Sie soll erstens die Skills anzeigen, also brauchen wir zumindest die Tabelle tblSkills. Um aber nur die Skills des aktuellen Mitarbeiters zu ermitteln, brauchen wir auch noch die Tabelle tblMitarbeiterSkills, welche die Skills über das Fremdschlüsselfeld MitarbeiterID mit dem jeweiligen Mitarbeiter verbindet. Die Tabelle tblMitarbeiter selbst brauchen wir nicht: Wir wollen im Listenfeld ja keine der eigentlichen Mitarbeiterdaten anzeigen, sondern nur die über die Tabelle tblMitarbeiterSkills verknüpften Einträge der Tabelle tblSkills. Erstellen Sie also eine neue Abfrage und fügen Sie die beiden Tabellen tblSkills und tblMitarbeiterSkills zur Abfrage hinzu (siehe [basics] Abfrage erstellen). Ziehen Sie die beiden Felder SkillID und Skill der Tabelle tblSkills und das Feld MitarbeiterID der Access [basics] 8/2010 www.access-basics.de Tabelle tblMitarbeiterSkills in das Entwurfsraster. Speichern Sie die Abfrage dann zunächst unter dem Namen qrySkillsEinesMitarbeiters. Dieser Name ist prinzipiell noch nicht korrekt: Die Abfrage zeigt nun nämlich alle Skills aller Mitarbeiter an. Damit nur die Skills des aktuell im Formular frmMitarbeiterSkills angezeigten Mitarbeiters anzeigt, müssen wir der Spalte MitarbeiterID der Abfrage ein Kriterium hinzufügen, das genau dem Wert von MitarbeiterID im Formular entspricht. Zeigt also das Formular den Mitarbeiter mit der MitarbeiterID 4711 an, soll die Abfrage für das Feld MitarbeiterID als Parameter den Wert 4711 erhalten. Die Abfrage sähe im Entwurf dann wie in Bild 6 aus. Bild 6: Datenherkunft des linken Listenfeldes mit vorläufigem Kriterium Nun können Sie diesen Wert natürlich nicht fest dort eintragen. Die Abfrage muss diesen Wert in Abhängigkeit des Wertes im Formular dort einsetzen. Das erledigen Sie durch einen Verweis auf das entsprechende Formularfeld. In diesem Fall lautet dieser so: Forms!frmMitarbeiterSkills!MitarbeiterID Die einzelnen Bestandteile sind schnell erläutert: Forms verweist auf die Auflistung aller geöffneten Formulare. Wenn Sie auf ein Element einer Auflistung selbstgebauter Objekte, also beispielsweise das Formular frmMitarbeiterSkills, zugreifen möchten, hängen Sie dieses Element durch ein Ausrufezeichen getrennt an die Auflistung an. frmMitarbeiterSkills enthält wiederum benutzerdefinierte Objekte, in diesem Fall das Steuerelement MitarbeiterID – auch diese hängen Sie mit führendem Ausrufezeichen an den bestehenden Ausdruck an (weitere Informationen siehe [basics] Formularelemente referenzieren). Bild 7: Endgültige Datenherkunft des Listenfeldes lstAusgewaehlt Beachten Sie, dass wir den Haken in der Zeile Anzeigen für die Spalte MitarbeiterID entfernt haben – das Feld dient nur als Kriterium und soll nicht im Listenfeld angezeigt werden. Auch das Feld SkillID soll dort eigentlich nicht erscheinen. Dennoch benötigen wir es: Wenn der Benutzer einen Eintrag eines Listenfeldes markiert und diesen zum anderen Listenfeld verschieben möchte, müssen wir diesen Datensatz schließlich eindeutig identifizieren können. Und das gelingt am besten mit dem Primärschlüsselfeld. Listenfeld füllen Damit das Listenfeld lstAusgewaehlt gefüllt wird, stellen Sie die Eigenschaft Datensatzherkunft des Seite 19 Bild 8: Das Listenfeld zeigt vorerst nur die Primärschlüsselwerte der verknüpften Skills an. Listenfeldes auf den Namen der soeben erstellten Abfrage ein, also auf qrySkillsEinesMitarbeiters. Ein Wechsel in die Formularansicht zeigt, dass wir das Listenfeld noch optimieren müssen (s. Bild 7). Da wir bereits einige Daten in die Tabellen tblSkills Access [basics] 8/2010 www.access-basics.de und tblMitarbeiterSkills eingetragen haben, zeigt es zwar bereits einige Daten an, aber leider nur das erste Feld der Datensatzherkunft. Es soll aber das zweite Feld anzeigen. Dies ändern wir durch Ändern der beiden Eigenschaften Spaltenanzahl und Spaltenbreiten auf die Werte 2 und 0cm. Ersteres sorgt dafür, dass theoretisch beide Felder im Listenfeld erscheinen, und Zweiteres blendet das erste der beiden Felder durch Einstellen der Breite auf 0cm gleich wieder aus. Durch das Auslassen einer Breite für das zweite, letzte Feld nimmt dieses den kompletten verbleibenden Platz ein. Das Ergebnis sieht nun schon viel besser aus, wie Bild 9 zeigt. Listenfeld mit den übrigen Skills Bild 9: Listenfeld mit korrekter Anzeige der Skill-Einträge Die Skills eines Mitarbeiters zu ermitteln war einfach: Wir brauchten einfach nur die aktuelle MitarbeiterID als Kriterium für das entsprechende Feld der Datensatzherkunft des Listenfeldes einzutragen. Das Auffinden der Einträge der Tabelle tblSkills, die dem aktuell angezeigten Mitarbeiter nicht zugeordnet sind, ist etwas schwieriger. Wir beginnen mit einer Abfrage namens qrySkillsNichtZugeordnet, die schlicht die Tabelle tblSkills enthält und beide Felder dieser Tabelle anzeigt. Bild 10 zeigt die Entwurfsansicht dieser Abfrage. Nun liefert diese alle Skills. Die Skills, die dem aktuellen Mitarbeiter zugeordnet sind, soll sie jedoch nicht anzeigen. Wie aber schließen wir genau diese aus? Dazu verwenden wir eine Kriterium, das genau die SkillIDs auflistet, die dem Mitarbeiter bereits zugeteilt wurden. Dieses Kriterium verwendet das INSchlüsselwort mit einer Abfrage als Parameter, die genau die gewünschten Daten liefert (grundlegende Informationen hierzu finden Sie in Abfragen für die Datenauswahl – Teil V: Filtern nach Zahlen). Diese Abfrage sieht als SQL-Ausdruck wie folgt aus: Bild 10: Diese Abfrage gibt zunächst alle Datensätze der Tabelle tblSkills zurück. SELECT SkillID FROM qrySkillsEinesMitarbeiters Die Abfrage liefert genau die gleichen Datensätze wie die Abfrage, die als Datensatzherkunft des Listenfeldes lstAusgewaehlt dient – allerdings beschränkt sie sich auf das Primärschlüsselfeld SkillID. Das ist auch notwendig, denn die IN-Klausel, welche das Ergebnis der Abfrage weiterverwenden soll, kann nur ein einziges Feld verarbeiten. Durch die Verwendung des Ausdrucks NOT IN (...) als Kriterium liefert die Abfrage qrySkillsNichtZugeordnet aus Bild 11 alle Datensätze der Tabelle tblSkills, Seite 20 Bild 11: Diese Abfrage liefert alle Skills, die dem aktuellen Mitarbeiter nicht zugeordnet wurden. deren SkillID nicht durch die in Klammern angegebene Abfrage geliefert werden. Stellen Sie nun die Datensatzherkunft des Listenfeldes lstNichtZugeordnet auf den Namen der neuen Abfrage qrySkillsNichtZugeordnet ein. Passen Sie außerdem wie schon beim ersten Listenfeld die Ei- Access [basics] 8/2010 www.access-basics.de genschaften Spaltenanzahl und Spaltenbreiten auf die Werte 2 und 0cm ein. Das Ergebnis liefert ein erneuter Wechseln in die Formularansicht (siehe Bild 12). Prima: Die Listenfelder zeigen bereits die richtigen Skills für den Mitarbeiter an und auch die nicht zugeordneten Skills stimmen. Listenfeldinhalte dynamisch anpassen Der Wechsel zum nächsten Mitarbeiterdatensatz bringt jedoch eine gewisse Enttäuschung mit sich: Die Listenfelder zeigen nämlich immer noch die gleichen Daten an. Das ist aber auch kein Wunder: Immerhin sind die Listenfelder in keiner Weise an den aktuellen Mitarbeiter-Datensatz gebunden. Allein die Datensatzherkunft des linken Listenfelds wird einmalig beim Öffnen des Formulars nach der aktuellen MitarbeiterID gefiltert und das rechte Listenfeld wird ebenfalls entsprechend gefüllt. Im Beitrag Steuerelemente – Teil II: Das Listenfeld haben Sie bereits einiges über Listenfelder erfahren. Diese lassen sich zum Beispiel mit der RequeryMethode aktualisieren. Das brauchen wir! Und wann wollen wir die Listenfelder aktualisieren? Immer beim Wechseln des Mitarbeiter-Datensatzes des Formulars. Unter dem Titel Formulare mit VBA programmieren – Teil I: Formularereignisse nutzen haben wir Ihnen bereits gezeigt, wie Sie grundsätzlich mit dem Ereignissen eines Formulars umgehen und eigenen VBACode durch bestimmte Ereignisse ausführen lassen können. In diesem Fall lernen Sie ein neues Ereignis kennen, nämlich Beim Anzeigen. Es wird jedesmal ausgelöst, wenn das Formular den angezeigten Datensatz wechselt. Dies geschieht einmal beim Öffnen des Formulars und dann bei jedem Datensatzwechsel. Fügen Sie gemäß der Anleitung in [basics] Ereignisprozedur hinzufügen eine Ereignisprozedur für das Ereignis Beim Anzeigen des Formulars hinzu. Dieses ergänzen Sie wie folgt: Private Sub Form_Current() Me!lstNichtZugeordnet.Requery Me!lstZugeordnet.Requery End Sub Bild 12: Das Listenfeld zeigt für den ersten Datensatz bereits die gewünschten Daten an. Die Prozedur aktualisiert bei jedem Datensatzwechseln die Datensatzherkunft der beiden Listenfelder – probieren Sie es aus! Skills hinzufügen Nun benötigen wir noch die Funktionen zum Hinzufügen und Entfernen der Skills eines Mitarbeiters. Beginnen wir mit dem Hinzufügen. Dieses soll durch entweder durch einen Klick auf die Schaltfläche mit dem Kleiner-Zeichen oder durch einen Doppelklick auf das rechte Listenfeld geschehen. Die Ereignisprozedur für die Schaltfläche cmdEinzelnHinzufuegen soll durch einen Mausklick ausgelöst werden. Wechseln Sie also in die Entwurfsansicht des Formulars, markieren Sie also die Schaltfläche und wählen Sie für die Eigenschaft Beim Klicken den Wert [Ereignisprozedur] aus. Die nach einem Klick auf die Schaltfläche mit den drei Punkten (...) erscheinende VBA-Prozedur erweitern Sie wie folgt: Private Sub cmdEinzelnHinzufuegen_Click() Dim lngMitarbeiterID As Long Dim lngSkillID As Long lngMitarbeiterID = Me!MitarbeiterID lngSkillID = Me!lstNichtZugeordnet CurrentDb.Execute "INSERT INTO µ tblMitarbeiterSkills(MitarbeiterID, µ SkillID) VALUES(" & lngMitarbeiterID µ & ", " & lngSkillID & ")", dbFailOnError Me!lstZugeordnet.Requery Me!lstNichtZugeordnet.Requery End Sub Uups! Das ist eine ganze Menge Code – zumindest, wenn Sie mit Access [basics] in die Access-Pro- Seite 21 Access [basics] 8/2010 www.access-basics.de grammierung einsteigen. Aber wir schauen uns alles in Ruhe an. Eigentlich erledigt diese Prozedur nur die folgenden Schritte: Sie deklariert zunächst zwei Variablen. Diese bekommen später den Primärschlüsselwert des aktuell ausgewählten Mitarbeiters und des im rechten Listenfeldes markierten Skills zugewiesen: Dim lngMitarbeiterID As Long Dim lngSkillID As Long Die MitarbeiterID erhalten wir über das gleichnamige Steuerelement des Formulars. Es wird in der Variablen lngMitarbeiterID gespeichert (mehr zu Variablen erfahren Sie in VBA-Programmierung – Teil II: Variablen in VBA): Skill per Doppelklick hinzufügen Die gleiche Aktion, die wir mit einem Klick auf die Schaltfläche cmdEinzelnHinzufuegen erreichen, soll auch durch einen Doppelklick auf einen der Einträge des rechten Listenfeldes ausgeführt werden. Also legen Sie eine neue Ereignisprozedur für das Ereignis Beim Doppeklicken des Listenfeld-Steuerelements an. Prinzipiell können Sie hier genau die gleichen Anweisungen unterbringen wie in der Ereignisprozedur cmdEinzelnHinzufuegen_Click. Das würde allerdings bedeuten, dass wir zweimal genau die gleiche Abfolge von VBA-Anweisungen in verschiedenen Routinen haben! Das ist aus verschiedenen Gründen nicht optimal, aber wir wollen diese Lösung vorerst akzeptieren. Später gehen wir darauf ein, wie Sie diese Routinen optimieren können. lngMitarbeiterID = Me!MitarbeiterID Die neue Ereignisprozedur sieht nun so aus: Auf ähnliche Weise erhalten wir die SkillID. Diese entspricht dem aktuell im Listenfeld lstNichtZugeordnet ausgewählten Eintrag: lngSkillID = Me!lstNichtZugeordnet Die folgende Anweisung erledigt nun die Hauptarbeit. Sie ruft eine SQL-Anfügeabfrage auf, die der Tabelle tblMitarbeiterSkills einen neuen Datensatz hinzufügt. Dabei füllt die Abfrage die beiden Felder MitarbeiterID und SkillID mit den in den Variablen lngMitarbeiterID und SkillID zwischengespeicherten Werten: CurrentDb.Execute "INSERT INTO µ tblMitarbeiterSkills(MitarbeiterID, µ SkillID) VALUES(" & lngMitarbeiterID µ & ", " & lngSkillID & ")", dbFailOnError Schließlich sollen nach dem Hinzufügen eines neuen Skills für den aktuellen Mitarbeiter noch die Datensatzherkünfte der beiden Listenfelder lstZugeordnet und lstNichtZugeordnet aktualisiert werden. Die dazu nötigen Anweisungen kennen Sie bereits: Me!lstZugeordnet.Requery Me!lstNichtZugeordnet.Requery Die Anweisung zum Durchführen der Anfügeabfrage wollen wir hier nicht bis ins letzte Detail besprechen, da wir in folgenden Artikeln noch auf dieses Thema eingehen – Gleiches gilt für die noch folgenden SQLAbfragen. Seite 22 Private Sub lstNichtZugeordnet_DblClick(Cancel µ As Integer) Dim lngMitarbeiterID As Long Dim lngSkillID As Long lngMitarbeiterID = Me!MitarbeiterID lngSkillID = Me!lstNichtZugeordnet CurrentDb.Execute "INSERT INTO µ tblMitarbeiterSkills(MitarbeiterID, µ SkillID) VALUES(" & lngMitarbeiterID µ & ", " & lngSkillID & ")", dbFailOnError Me!lstZugeordnet.Requery Me!lstNichtZugeordnet.Requery End Sub Skills entfernen Damit Sie die beim Ausprobieren hinzugefügten Skills auch wieder vom Mitarbeiter entfernen können, kümmern wir uns nun um die Ereignisprozedur Beim Klicken der Schaltfläche cmdEinzelnEntfernen. Die Prozedur sieht fast genauso wie die vorherigen beiden aus: Private Sub cmdEinzelnEntfernen_Click() Dim lngMitarbeiterID As Long Dim lngSkillID As Long lngMitarbeiterID = Me!MitarbeiterID lngSkillID = Me!lstZugeordnet CurrentDb.Execute "DELETE FROM µ tblMitarbeiterSkills WHERE MitarbeiterID = µ " & lngMitarbeiterID & " AND SkillID = " µ & lngSkillID, dbFailOnError Access [basics] 8/2010 www.access-basics.de gen Index für die Kombination dieser beiden Felder angelegt haben. Me!lstZugeordnet.Requery Me!lstNichtZugeordnet.Requery End Sub Es gibt jedoch zwei wesentliche Unterschiede: • Der Wert für die Variable lngSkillID wird aus dem Listenfeld lstZugeordnet gewonnen. • Es wird keine Anfügeabfrage ausgeführt, sondern eine Löschabfrage. Diese entfernt genau den Datensatz aus der Tabelle tblMitarbeiterSkills, dessen Felder MitarbeiterID und SkillID die in den Variablen lngMitarbeiterID und lngSkillID enthaltenen Werte aufweisen. Entfernen eines Skills per Doppelklick Auch hier wollen wir das Verschieben eines Eintrags von Listenfeld zu Listenfeld durch einen Doppelklick erlauben. Dazu legen wir genau die gleich Zeilen wie aus der obigen Prozedur in einer weiteren Prozedur an, die durch das Ereignis Beim Doppelklicken des Listenfeldes lstZugeordnet ausgelöst wird: Private Sub cmdEinzelnEntfernen_Click() Dim lngMitarbeiterID As Long Dim lngSkillID As Long lngMitarbeiterID = Me!MitarbeiterID lngSkillID = Me!lstZugeordnet CurrentDb.Execute "DELETE FROM µ tblMitarbeiterSkills WHERE MitarbeiterID µ = " & lngMitarbeiterID & " AND µ SkillID = " & lngSkillID, dbFailOnError Me!lstZugeordnet.Requery Me!lstNichtZugeordnet.Requery End Sub Fehler vermeiden Damit hätten wir die Grundfunktion fast fertig. Wenn da nicht noch ein paar kleine Fehler auftreten würden: • Wenn Sie direkt auf die Schaltfläche mit der Beschriftung < klicken, ohne zuvor einen Eintrag des rechten Listenfeldes zu markieren, löst dies einen Fehler 94, Unzulässige Verwendung von Null aus. • Wenn man einen Eintrag des rechten Listenfelds markiert und dann zweimal auf die Schaltfläche mit der Beschriftung < klickt, folgt ein Fehler mit der Nummer 3022. Dieser wird durch den Versuch ausgelöst, eine bereits vorhandene Kombination aus MitarbeiterID und SkillID erneut in der Tabelle tblMitarbeiterSkills zu speichern. Dies funktioniert nicht, weil wir einen eindeuti- Seite 23 Der erste Fehler resultiert daraus, dass das Listenfeld erst einen Wert enthält, wenn der Benutzer einen Eintrag markiert hat. Dies ist zu Beginn nicht der Fall. Der zweite Fehler ist ein Bug: Es war ein Eintrag im Listenfeld markiert, der aber aus der Datensatzherkunft entfernt wurde. Durch den Aufruf der RequeryMethode wird zwar der betroffene Listenfeldeintrag entfernt, der Primärschlüsselwert des zuletzt markierten Eintrags bleibt jedoch als aktueller Wert des Listenfeldes gespeichert. Es gibt diverse Möglichkeiten, diese beiden Fehler zu umgehen. Damit wir diesen Artikel bis zur Fortsetzung beschließen können und Sie dennoch eine funktionierende Lösung haben, wählen wir ausnahmsweise einen unsauberen Weg: Wir fügen einfach zu Beginn der betroffenen Prozedur die Zeile On Error Resume Next ein. Das sieht dann so aus: Private Sub cmdEinzelnHinzufuegen_Click() Dim lngMitarbeiterID As Long Dim lngSkillID As Long On Error Resume Next lngMitarbeiterID = Me!MitarbeiterID ... End Sub Zusammenfassung und Ausblick Diese Lösung ist für den Kenntnisstand, den Sie durch den bisherigen Inhalt von Access [basics] erhalten haben, bereits recht anspruchsvoll. Allerdings lohnt es sich, hier nicht aufzugeben: Wenn Sie das Verwalten von m:n-Beziehungen mit Listenfeldern einmal verstanden haben, können Sie es mit nur wenigen Änderungen immer wieder einsetzen. In der Fortsetzung zu diesem Artikel werden wir uns um das Hinzufügen und Entfernen aller Skills gleichzeitig kümmern und einige Feinarbeiten am Code vornehmen.