Karl Donaubauer, Wien
In der Access-FAQ von Karl Donaubauer (www.donkarl.com) finden Sie die meistgestellten Fragen und Anworten zum Thema Microsoft Access. In dieser Beitragsreihe stellt Karl Donaubauer die wichtigsten Einträge im Detail vor und zeigt Ihnen entsprechende Lösungen anhand praxisnaher Beispiele. Im fünften Teil lernen Sie die Lösungen zu den meistgenannten Problemen der Teilnehmer der deutschsprachigen Access-Newsgroups im Zusammenhang mit Abfragen kennen.
Eine recht häufige Aufgabe bei der Arbeit mit Datenbanken ist der Vergleich der Daten zweier Tabellen. Dabei gibt es zwei übliche Szenarien. Im ersten geht es um den Vergleich zweier “gleichrangiger” Tabellen, die ähnlich aufgebaut sind – zum Beispiel wenn beim Import von externen Daten geprüft werden muss, ob und welche Daten in der bestehenden Tabelle bereits vorhanden beziehungsweise welche Datensätze in der Importtabelle neu sind. Das zweite Szenario ist die 1:n-Beziehung, bei der man feststellen möchte, welche Daten der Mastertabelle in der Detailtabelle bereits vorkommen oder welche noch nicht. Typische Anwendungsfälle sind etwa, aus einer Adresstabelle (Mastertabelle) jene Adressen herauszufinden, die nicht in der Bestelltabelle (Detailtabelle) vorkommen, oder Firmen (Mastertabelle), bei denen es noch keine Ansprechpartner (Detailtabelle) gibt.
Bild 1: Verknüpfungseigenschaften
In beiden Szenarien lässt sich die Aufgabe im Prinzip darauf reduzieren herauszufinden, welche Daten sich in Tabelle A befinden, aber nicht in Tabelle B.
Bild 1 zeigt eine Abfrage aus der Beispiel-Datenbank zu diesem Artikel mit der Firmen- und der Ansprechpartnertabelle.
Wenn die Beziehungen in der Datenbank richtig gesetzt wurden, dann zeigt Access in der Abfrage mit den beteiligten Tabellen automatisch für die Verknüpfungsfelder (hier FirmenId) eine Verknüpfung ohne Pfeil an.
Ohne Beziehungen auf Datenbankebene müssen Sie selber mit der Maus durch Ziehen und Fallenlassen eines Verknüpfungsfeldes über dem anderen eine solche pfeillose Verknüpfungslinie erzeugen. Mit dieser Verknüpfung (INNER JOIN in SQL) bekommt man im Ergebnis nur jene Firmen, für die es bereits Ansprechpartner gibt.
Hinweis
Auf der beiliegenden Heft-CD finden Sie Beispiel-Datenbanken für Access 97 und Access 2000 und höher mit den Quellcodes und Beispielen aus diesem Beitrag.
Durch Doppelklick auf die Beziehungslinie öffnet sich das Dialogfenster “Verknüpfungseigenschaften”. Option 1 ist der bereits angeführte INNER JOIN, Option 2 der LEFT JOIN, Option 3 der RIGHT JOIN. Im Beispiel ist ein LEFT JOIN gefragt, also Pfeil von links, denn es geht im ersten Schritt darum, alle Firmen anzuzeigen, egal, ob es bereits Ansprechpartner gibt oder nicht. Welche Felder aus der Firmentabelle zur Anzeige ausgewählt werden, ist an sich egal. Im Beispiel ist es nur der Firmenname. Der zweite wichtige Schritt – neben der richtigen Einstellung der Verknüpfung – ist die Auswahl des Verknüpfungsfeldes der n-Seite (im Beispiel FirmenId aus der Tabelle tbl_Ansprechpartner) mit dem Kriterium Ist Null.
Als Ergebnis erscheinen nur noch jene Firmennamen, für die es keine Ansprechpartner gibt. Der SQL-Text dieser Abfrage lautet:
SELECT tbl_Firmen.Firma FROM tbl_Firmen LEFT JOIN tbl_AnsprechPartner ON tbl_Firmen.FirmenId = tbl_AnsprechPartner.FirmenId WHERE tbl_AnsprechPartner.FirmenId Is Null;
oder schematisch:
SELECT A.* FROM A LEFT JOIN B ON A.ID = B.ID WHERE B.ID Is Null
Es gibt verschiedene Methoden, um für die Ergebnis-Datensätze einer Abfrage eine laufende Nummer zu erzeugen. Zwei davon stelle ich hier vor. Eine, die reines SQL verwendet, und eine andere, die auf die Domänenaggregatfunktionen von Access zugreift. Welche der beiden Varianten Sie wählen, sollten Sie vor allem von einem Geschwindigkeitstest mit Ihren konkreten Daten abhängig machen.
In der Beispiel-Datenbank finden Sie eine Tabelle tblArtikel mit den Feldern ArtikelId, Bezeichnung, Stueck und Preis. Im ersten Beispiel soll die Nummerierung nach ArtikelId aufsteigend erfolgen. ArtikelId ist ein Autowertfeld mit den üblichen Lücken durch gelöschte Datensätze. Für die laufende Nummer ist in der Abfrage ein neues Feld notwendig, in dem sich eine Unterabfrage befindet. Der Text im Feld lautet:
RowNum: (SELECT COUNT (*) FROM [tblArtikel] AS Temp WHERE [Temp].[ArtikelId] < [tblArtikel].[ArtikelId])+1
Bild 2: Laufende Nummer über ArtikelId
Das Ergebnis sehen Sie in Bild 2. RowNum ist dabei ein beliebiger Name für das berechnete Feld, Temp ein beliebiger Name für die temporäre Tabelle in der Unterabfrage, die eine Kopie der echten Tabelle darstellt. Der SQL-Text zählt, wie viele Datensätze es in dieser temporären Tabelle gibt, deren ArtikelId kleiner ist als die ArtikelId der echten Tabelle im aktuellen Abfrage-Datensatz. Zu diesem Wert wird dann außerhalb der Unterabfrage noch 1 addiert.
Beispiele: Im ersten Datensatz ist die aktuelle ArtikelId 1. Es gibt 0 Datensätze in der Tabellenkopie mit kleineren Werten für ArtikelId. Der Ausdruck [tblArtikel].[ArtikelId])+1 ergibt also den Wert 1 für das Feld RowNum. Im zweiten Datensatz hat das Feld ArtikelId den Wert 2. Es gibt einen Datensatz mit einem kleineren Wert für das Feld ArtikelId. Der Ausdruck [tblArtikel].[ArtikelId])+1 ergibt also in diesem Fall den Wert 2. Im dritten Datensatz hat das Feld ArtikelId den Wert 5. Es gibt aber nur zwei Datensätze mit kleineren Werten für das Feld ArtikelId, der entsprechende Ausdruck ergibt also den Wert 3. Die Lücke im Feldwert spielt für den neuen Zähler also keine Rolle.
Eine andere Variante ist die Verwendung der DomAnzahl-Funktion (englisch: DCount). In der Beispiel-Datenbank finden Sie jede Abfrage einmal mit SQL- einmal mit DCount-Zähler. Das Prinzip ist hier das gleiche wie bei der SQL-Variante. Das Feld für den laufenden Zähler nach ArtikelId sieht dann jedoch so aus:
RowNum: DomAnzahl("ArtikelId"; "tblArtikel";"ArtikelId < " & [ArtikelId])+1
Bei gleichen Werten in zwei Datensätzen wird der Rang zweimal vergeben und der nächste ausgelassen. Im folgenden Beispiel gibt es zweimal den Preis 300,00. Um einen Zähler aufgrund der Preise zu erzeugen, muss man die Unterabfrage nur leicht abändern:
RowNum: (SELECT COUNT (*) FROM [tblArtikel] AS Temp WHERE [Temp].[Preis] < [tblArtikel].[Preis] )+1
beziehungsweise:
RowNum: DomAnzahl("Preis"; "tblArtikel";"Preis < " & [Preis])+1
Bild 3 zeigt das Ergebnis mit dem doppelt auftretenden Wert 2 und dem ausgelassenen Wert 3 im Zähler.
Die fortlaufende Nummer entspricht also der Wertung bei Sportereignissen, die auch einer der praktischen Anwendungsfälle für diese Technik ist.
Wenn es eine andere Sortierung oder andere einschränkende Kriterien für die Datensätze gibt, so müssen diese auch in der Unterabfrage beziehungsweise im DomAnzahl-Ausdruck vorgenommen werden. Lautet zum Beispiel das Kriterium in der Preis-Spalte >300, dann muss das Feld für die laufende Nummer so aussehen:
RowNum: (SELECT COUNT (*) FROM [tblArtikel] AS Temp WHERE [Temp].[Preis] > 300 AND [Temp].[Preis] < [tblArtikel].[Preis] )+1
beziehungsweise mit DomAnzahl:
RowNum: DomAnzahl("Preis";"tblArtikel"; "Preis > 300 AND Preis < " & [Preis])+1
Bild 3: Fortlaufende Nummer mit doppelten Werten
Bild 4 zeigt das Ergebnis. Die weggefilterten Datensätze spielen für die laufende Nummer also keine Rolle.