3. Tabfragen

Tabellen und Abfragen

3.1   Tabellen wieder einbinden 3.11 Laufende Nummer/Summe in Abfragen
3.2   Autowert zurücksetzen 3.12 Geburtstagsliste erstellen
3.3   Funktion im Standardwert 3.13 Datumskriterium funktioniert nicht
3.4   Prüfen ob Tabelle vorhanden 3.14 Kriterium soll alle Datensätze bringen
3.5   Datentyp eines Feldes per Code ändern 3.15 Variablen in Abfragen verwenden
3.6   Datensatz zu groß 3.16 Datensätze aus A, die nicht in B sind
  3.17 Zufällige Reihenfolge der Datensätze erreichen
  3.18 Parameter in Kreuztabellenabfragen
  3.19 Groß- und Kleinschreibung unterscheiden
   
3.1 Tabellen wieder einbinden
https://www.donkarl.com?FAQ3.1 aktualisiert 2014-01-04

Problem

Eine MDB enthält eingebundene Tabellen. Ändert sich das Verzeichnis der Herkunfts-DB dieser Tabellen, dann geht die Verbindung verloren.

Lösung

Die einfachste Variante ist natürlich, die eingebundenen Tabellen zu löschen und über den Menüpunkt Datei/Externe Daten/Tabellen verknüpfen neu einzubinden.

Eine weitere Methode ist die Verwendung des Tabellenverknüpfungs-Managers, der im Menü Extras/Datenbank-Dienstprogramme zu finden ist.

Wenn die Wiedereinbindung dynamisch mit VBA passieren soll, gibt's die Möglichkeit, einen Dateidialog zu verwenden und Code wie in:
http://www.mvps.org/access/tables/tbl0009.htm

Eine Komplettlösung samt Dateidialog und auch für mehrere Backends bietet der JStreet Access Relinker: http://www.jstreettech.com/downloads.aspx

Wenn sicher ist, dass sich beide DBs im gleichen Verzeichnis befinden, lässt sich die Wiedereinbindung auch komplett mit VBA automatisieren. Im Autoexec-Makro oder beim Öffnen des ersten Formulares kann z.B. folgender Code aufgerufen werden:

'******* CODE START *******
On Error GoTo MyError

Dim db As DAO.Database
Dim strDaten As String
Dim i as Integer

Set db = CurrentDB()

strDaten = Left(db.Name, Len(db.Name) - Len(Dir(db.Name))) & "DeineDaten.mdb"

For i = 0 To db.TableDefs.Count - 1
  If db.TableDefs(i).Connect <> "" Then
    If Mid(db.TableDefs(i).Connect, 11) <> strDaten Then
      db.TableDefs(i).Connect = ";database=" & strDaten
      db.TableDefs(i).RefreshLink
    End If
  End If
Next i

MyExit:
  Exit Sub

MyError:
  MsgBox "Bei der Installation ist eine Ausnahme aufgetreten. ", 16, "Ausnahme"
  Resume MyExit

'******* CODE ENDE *******

nach oben

3.2 Autowert zurücksetzen
https://www.donkarl.com?FAQ3.2 aktualisiert 2008-12-12

Problem

Du möchtest ein Autowert-Feld einer Tabelle wieder auf 1 oder den nächsthöheren freien Wert zurücksetzen, nachdem Datensätze eingegeben und wieder gelöscht wurden.

Lösung

Komprimiere die Datenbank: Menü Extras/Datenbank-Dienstprogramme/Datenbank komprimieren
Das setzt die Autowerte aller Tabellen auf den nächsthöheren freien Wert zurück.

In Versionen >=A00 funktioniert das oft nicht mehr. s. http://support.microsoft.com/?kbid=287756
Die dort vorgestellten Lösungen sind allerdings unnötig kompliziert, denn ab JET4, d.h. ab A00, kann man Startwert und Schrittweite (Seed und Increment) eines Autowertfeldes per SQL einstellen:

ALTER TABLE Tabelle ALTER COLUMN Feld COUNTER(1,1)

Die erste Zahl setzt den Startwert auf 1 zurück, die zweite stellt die Schrittweite auf 1 ein (kann man auch weglassen, inkl. Komma).

Autowerte sind übrigens für interne Zwecke gedacht, z.B. als Schlüssel- und Beziehungsfelder, nicht für die Erzeugung schöner Nummern. Die sollte man per Programmierung, über Standardwerte oder zu Fuß erzeugen.

nach oben

3.3 Funktion im Standardwert
https://www.donkarl.com?FAQ3.3

Problem

Du möchtest als Standardwert eines Tabellenfeldes eine Funktion wie z.B. AktuellerBenutzer (bzw. CurrentUser()) oder eine selbst erstellte VBA-Funktion oder einen Bezug auf ein Formularfeld etc. verwenden. Entweder wird die Funktion in einen Text umgewandelt (Access fügt " " drumherum) oder es kommt eine Fehlermeldung: Standardwert wird nicht erkannt, unbekannte Funktion o.ä.

Ursache

Im Standardwert eines Tabellenfeldes sind benutzerdefinierte Funktionen oder Aggregatfunktionen von Access (DomWert etc.) sowie best. Funktionen wie CurrentUser oder Eval nicht möglich. Ebenso funktionieren Verweise auf Access-Objekte nicht.

Lösung

Verwende Formulare. Nur bei Steuerelementen von Formularen sind o.a. Funktionen bzw. Bezüge als Standardwert möglich.

nach oben

3.4 Prüfen ob Tabelle vorhanden
https://www.donkarl.com?FAQ3.4

Problem

Du möchtest automatisiert prüfen, ob eine bestimmte Tabelle bereits in der DB vorhanden ist.

Lösung

Dafür gibt es mehrere Varianten. Man kann z.B. den Fehler abfangen, der bei Aufruf einer nicht vorhandenen Tabelle erzeugt wird. Sauberer ist aber ein Überprüfen der TableDefs-Auflistung. Dazu kannst du folgende Funktion in ein Standardmodul kopieren:

Function fctTableExists(strTableName As String) As Boolean

  Dim db As DAO.Database
  Dim tdf As DAO.TableDef
  Set db = CurrentDb
  For Each tdf In db.TableDefs
    If tdf.Name = strTableName Then fctTableExists = True: Exit For
  Next

End Function


Eine andere Methode ist die Verwendung der Systemtabelle MSysObjects:

Function fctTableExists(strTableName As String) As Boolean

  If DCount("*", "MSysObjects", "Name='" & strTableName & "'") Then fctTableExists = True
    
End Function


Aufruf in beiden Fällen dann irgendwo in der DB mit:
fctTableExists("DeinTabellenname")

nach oben

3.5 Datentyp eines Feldes per Code ändern
https://www.donkarl.com?FAQ3.5

Problem

Du möchtest programmatorisch den Typ oder die Größe eines Feldes ändern.

Lösung

Vor der Version A00 ging das nur mit umständlicher Programmierung. Beispiel für das mögliche Vorgehen beim Ändern des Typs eines Feldes in ein Textfeld bzw. Ändern der Feldgröße eines Textfeldes:

'************ CODE START ************
Dim db As DAO.Database
'falls sich die Tabelle in der aktuellen DB befindet
Set db = CurrentDb
'falls sich die Tabelle in einer anderen DB als der aktuellen befindet z.B. bei Frontend/Backend-Aufteilung
'Set db = DBEngine.Workspaces(0).OpenDatabase("c:\Pfad\Andere.mdb")

'ursprüngliches Feld umbenennen
db.TableDefs("MeineTabelle").Fields("MeinFeld").Name = "AltesFeld"

'neues Feld mit gewünschten Einstellungen erzeugen
db.Execute "ALTER TABLE MeineTabelle ADD COLUMN MeinFeld VARCHAR(100)"

'alte Werte übernehmen
db.Execute "UPDATE MeineTabelle SET MeinFeld = AltesFeld"

'altes Feld löschen
db.Execute "ALTER TABLE MeineTabelle DROP COLUMN AltesFeld"

'************ CODE ENDE ************

Seit JET4, d.h. ab Version A00, ist dieses komplizierte Vorgehen nicht mehr nötig. Der ALTER TABLE-Befehl von Access-SQL wurde um ALTER COLUMN erweitert (s. <F1>). Für die gleiche Aufgabe reicht daher (mit obiger db-Variable) die Zeile:

db.Execute "ALTER TABLE MeineTabelle ALTER COLUMN MeinFeld VARCHAR(100)"

nach oben

3.6 Datensatz zu groß
https://www.donkarl.com?FAQ3.6

Problem

Beim Versuch, einen Datensatz zu speichern, erscheint der Fehler 3047 "Datensatz zu groß."

Ursache

Ein Datensatz einer Access-(d.h. Jet-)Tabelle ist mit ca. 2000 Zeichen begrenzt. In Versionen >=A00 erhöht sich dieses Limit auf ca. 4000 Zeichen, wenn bei Textfeldern die Eigenschaft Unicode-Kompression auf Ja eingestellt ist.
Dabei kommt es nie auf die Eigenschaft Feldgröße an, sondern auf die tatsächlich enthaltenen Zeichen.

Lösung

Memofelder werden bei dieser Begrenzung nicht eingerechnet, weil sie anders verwaltet werden. Du kannst also evtl. die Grenze umgehen, wenn du einige der (größeren) Textfelder in Memos umwandelst.

Ansonsten kannst du die Tabelle auch noch in mehrere gleichartige Tabellen aufteilen und zwischen diesen Tabellen eine 1:1-Beziehung setzen.

nach oben

3.11 Laufende Nummer/Summe in Abfragen
https://www.donkarl.com?FAQ3.11 aktualisiert 2021-10-25

Problem

Du möchtest in einer Abfrage ein Feld mit einer fortlaufenden Nummer oder fortlaufenden Summe haben.

Lösung

Laufende Nummer

Du kannst innerhalb deiner Abfrage eine Unterabfrage zum Erzeugen eines Zählerfeldes verwenden. Dazu gibst du in einer eigenen Spalte einen SQL-Text wie den folgenden ein:

LaufNummer: (Select Count (*) FROM [Artikel] as Temp WHERE [Temp].[Id] < [Artikel].[Id])+1

Das Beispiel zeigt, wie man für eine Tabelle "Artikel" mit einem eindeutigen Schlüsselfeld "Id" in der Abfrage ein Zählerfeld erzeugt. Wenn es in deiner Abfrage einschränkende Kriterien gibt oder Du nach anderen Kriterien sortiert hast, wird der SQL-Ausdruck etwas komplizierter:
mein deutscher KB-Artikel dazu im Webarchiv

Eine Alternative zu der Lösung mit der Unterabfrage ist, die laufende Nummer mit DCount (DomAnzahl) zu erstellen:
LaufNummer: DomAnzahl("Id";"Artikel";"Id<" & [Id])+1

Da Unterabfragen mit JET nicht gerade Boliden sind, kann diese Version trotz der oft verpönten Domänenaggregatfunktion bei großen Datenmengen durchaus schneller sein. Also, am besten testen.

Laufende Summe

geht z.B. mithilfe der DSum-Funktion:
Laufende Summe in Abfragen
Gruppierte Laufende Summe in Abfragen

nach oben

3.12 Geburtstagsliste erstellen
https://www.donkarl.com?FAQ3.12 aktualisiert 2021-10-25

Problem

Du möchtest eine Abfrage erstellen, um auf Basis eines Geburtsdatum-Feldes zu ermitteln, wer von den Leuten in deiner Tabelle z.B. in den nächsten 10 Tagen Geburtstag haben wird.

Lösung

Du kannst in einer Abfrage in einer neuen Spalte die Geburtstage ins aktuelle Jahr transferieren:
GebHeuer: DatAdd("jjjj";nz(DatDiff("jjjj";[Geburtsdatum];Datum()));[Geburtsdatum])

Dann schreibst du noch als Kriterium:
Zwischen Datum() Und Datum()+10

Diese Variante berücksichtigt natürlich nur Geburtstage im heurigen Jahr. Falls die Sache auch über den Jahreswechsel funktionieren soll, kannst du noch ein zweites berechnetes Feld erzeugen:
GebFolgeJahr: DatAdd("jjjj";nz(DatDiff("jjjj";[Geburtsdatum];Datum()))+1;[Geburtsdatum])

Bei diesem Feld das gleiche Kriterium wie oben verwenden, aber eine Zeile tiefer, also als Oder-Kriterium.

s.a. Alter ermitteln und mein deutscher KB-Artikel dazu im Webarchiv

nach oben

3.13 Datumskriterium funktioniert nicht
https://www.donkarl.com?FAQ3.13

Problem

Du verwendest in einer Abfrage als Kriterium einen Ausdruck wie =Datum() und keiner der Datensätze, die dem Kriterium entsprechen sollten, wird zurückgegeben.

Ursache

Die häufigste Ursache ist, dass im Tabellenfeld auch eine Zeit >00:00 Uhr mit gespeichert ist. Wie die Anzeige formatiert ist, ist dann egal, die Zeit wird immer mitgeführt. Das Problem wird meistens dadurch verursacht, dass das Feld irgendwo mit =Now bzw. =Jetzt() gefüllt wurde. Wenn man dieses Feld denn mit =Datum() vergleicht, so wird es immer mit 00:00 Uhr des aktuellen Tages verglichen.

Lösung

Verwende zum automatisierten Füllen von Datumsfeldern, die keine Zeit (bzw. 00:00) enthalten sollen, die Funktion Date statt der Funktion Now.
oder
Verwende für alle Datensätze mit heutigem Datum - unabhängig von der Uhrzeit - in der Abfrage als Kriterium:
>= Datum() UND < Datum()+1
oder
Verwende in der Abfrage beim Feld: Format(DeinFeld;"ttmmjjjj")
und im Kriterium entsprechend: Format(Datum();"ttmmjjjj")

nach oben

3.14 Kriterium soll alle Datensätze bringen
https://www.donkarl.com?FAQ3.14 aktualisiert 2021-10-25

Problem

Du möchtest in einer Abfrage bei einem best. Feld ein Kriterium verwenden z.B. einen Bezug auf ein Formularfeld oder einen Parameter. Normalerweise sollen die Datensätze entsprechend diesem Wert gefiltert werden.
Wenn das Kriterium aber leer ist oder einen bestimmten Wert hat (z.B. "*" oder "Alle") dann sollen alle Datensätze der Abfrage erscheinen.

Lösung

Du kannst als Kriterium schreiben:

für ein Steuerelement in einem Formular
[Forms]![DeinFormular]![DeinFeld] Oder [Forms]![DeinFormular]![DeinFeld] Ist Null

für einen leeren Parameter
[DeinParameter] Oder [DeinParameter] Ist Null

für den * (Stern, Asterisk) aus einem Formularfeld
[Forms]![DeinFormular]![DeinFeld] Oder [Forms]![DeinFormular]![DeinFeld] = "*"

bei einem Parameter "Alle"
[DeinParameter] Oder [DeinParameter] = "Alle"

usw.

Wenn der Oder-Teil in den Beispielen Wahr ist, dann werden alle Datensätze der Abfrage zurückgeliefert. Andernfalls werden die Datensätze ganz normal durch den Wert im ersten Teil des Kriterienausdrucks gefiltert.

Diese Methode funktioniert nur, wenn sie für wenige Spalten einer Abfrage angewandt wird. Bei mehr als ein paar Kriterien dieser Art hat Access/JET meist Probleme, die Abfrage auszuführen. In diesem Fall ist es besser, einen SQL-Text per Code zu basteln, der nur die Kriterien beinhaltet, die einen Wert haben. Das verbessert sowohl das SQL-Statement als auch die Performance.

mein deutscher KB-Artikel dazu im Webarchiv

nach oben

3.15 Variablen in Abfragen verwenden
https://www.donkarl.com?FAQ3.15

Problem

Du möchtest eine globale Variable in einer Abfrage verwenden, z.B. als Kriterium oder Teil eines berechneten Feldes. Wenn du das versuchst, wird die Variable nicht erkannt sondern als String oder Parameter angesehen.

Lösung

Du kannst eine Variable nicht direkt in einer Abfrage verwenden. Erzeuge stattdessen in einem Standardmodul eine Funktion, die nichts anderes tut, als den Wert der Variablen zurückzugeben. z.B.

Public Function fctSendVar() ' hier evtl. noch den Typ angeben

  fctSendVar = DeineGlobaleVariable

End Function


In der Abfrage schreibst du dann statt der Variablen den Namen der Funktion inkl. Klammern.

Auf die gleiche Weise kannst du übrigens auch den Wert einer Variablen als Steuerelementinhalt in Formularen oder Berichten verwenden.

nach oben

3.16 Datensätze aus A, die nicht in B sind
https://www.donkarl.com?FAQ3.16

Problem

Du hast 2 Tabellen "A" und "B" und möchtest herausfinden, welche Datensätze sich in A befinden, aber nicht in B.

Lösung

Verknüpfe die Tabellen im Abfrageentwurf über dafür geeignete Felder (Primärschlüssel und Fremdschlüssel o.ä.) und mach einen Doppelklick auf die Verknüpfungslinie. Dort stellst du die zweite Variante an Beziehung ein, also jene, die alle DS aus Tabelle A bringt und nur die aus Tabelle B usw… Hol den Stern aus Tabelle A als Feld in die Abfrage und das Primärschlüsselfeld aus Tabelle B als weiteres Feld. Dieses Feld brauchst du nicht anzeigen zu lassen, aber als Kritierum schreibst du: Ist Null

Das SQL-Statement sieht dann ca. so aus:

SELECT A.*
FROM A LEFT JOIN B ON A.Id = B.Id
WHERE B.Id Is Null

nach oben

3.17 Zufällige Reihenfolge der Datensätze erreichen
https://www.donkarl.com?FAQ3.17

Problem

Du möchtest in einer Abfrage erreichen, dass sich die Reihenfolge der Datensätze nach Zufallsprinzip bei jedem Aufruf ändert.

Lösung

Für die Erzeugung von Zufallszahlen bietet VBA die Randomize-Funktion Rnd() (in Abfragen heißt sie ZZG).
Um eine zufällige Sortierung zu erreichen, kannst du ein neues Feld erzeugen und darin ein Autowert-Feld verwenden, das in der zugrundeliegenden Tabelle hoffentlich existiert (falls nicht, dann erzeugen).
Der Ausdruck für das neue Feld lautet dann: Rnd(MeinAutowertFeld)
Nach diesem Feld sortieren lassen.

nach oben

3.18 Parameter in Kreuztabellenabfragen
https://www.donkarl.com?FAQ3.18

Problem

Du verwendest in einer Kreuztabellenabfrage einen Parameter, den du über ein Parameterfenster eingeben möchtest oder z.B. einen Formularbezug als Kriterium, der bei anderen Arten von Abfragen tadellos funktioniert. Bei der Kreuztabellenabfrage aber erscheint die Fehlermeldung:
"Das Microsoft Jet-Datenbankmodul erkennt <Name> nicht als gültigen Feldnamen oder Ausdruck. (Fehler 3070)"

Ursache

Bei einer Kreuztabellenabfrage werden die Spaltennamen dynamisch erzeugt. Daher weiß Access nicht, ob es sich um einen Parameter oder einen Feldbezug handelt. Erst später, beim Versuch von JET, die Spalte an ein Tabellenfeld zu binden, kommt es zu dem Fehler.

Lösung

Trage den Parameter, Formularbezug etc. und den Datentyp zusätzlich in die Parameterliste der Kreuztabellenabfrage ein. Im Abfrageentwurf Menü Abfrage/Parameter. Damit kennt Access Name und Typ des Parameters und geht von Anfang an richtig damit um.

nach oben

3.19 Groß- und Kleinschreibung unterscheiden
https://www.donkarl.com?FAQ3.19 aktualisiert 2005-11-27

Problem

Du möchtest bei einem Kriterium oder bei einer Verknüpfung unterscheiden, ob ein Buchstabe groß oder klein geschrieben ist. Access interessiert das aber nicht.

Ursache

JET, die Standard-Datenbank-Engine von Access, unterscheidet nicht zwischen Groß- und Kleinschreibung, ist also nicht "case-sensitive".

Lösung

Du kannst die VBA-Funktion StrComp() verwenden, um zwischen Groß- und Kleinschreibung (übrigens auch zwischen "ss" und "ß") zu unterscheiden. Angenommen, FeldX soll mit einem Kriterium "abc" verglichen werden. Als Ergebnis soll nur diese klein geschriebene Variante kommen, nicht "ABC", "Abc", "aBc" etc. Dafür legst du ein neues Abfragefeld an, in dem folgender Ausdruck steht:

StrComp([FeldX];"abc";0)

Die 0 hinten (für den Parameter Compare) sorgt für den nötigen binären Vergleich. Nur wenn die verglichenen Werte in ihrer Schreibweise exakt übereinstimmen, liefert StrComp() den Rückgabewert 0 (s. <F1> zu "StrComp"). Du kannst daher die Treffer rausfiltern, indem du in der Kriterienzeile des Feldes schreibst: 0
s.a. http://support.microsoft.com/?kbid=209674

MIt dem gleichen Vorgehen kann man bei einer Verknüpfung zwischen Groß- und Kleinschreibung in den verknüpfenden Feldern unterscheiden, indem man eben wie o.a. ein zusätzliches Feld mit dem binären StrComp-Vergleich für die beiden Verknüpfungsfelder einsetzt. In SQL wäre das z.B.:

SELECT *
FROM Tabelle1 INNER JOIN Tabelle2 ON Tabelle1.FeldX = Tabelle2.FeldX
WHERE StrComp(Tabelle1.FeldX, Tabelle2.FeldX, 0) = 0


s.a. http://support.microsoft.com/?kbid=244693

nach oben