infobase: EDV - MS-Access


Datensätze

Anzahl   Quelle: dmt   Datum: 03.2004   nach oben

ANZAHL DATENSÄTZE / RECORDCOUNT:

Die Eigenschaft RecordCount eines Recordsets gibt manchmal nur die Anzahl der Datensätze zurück, auf die zugegriffen wurde !

So erhält man nach einer erfolgreichen Abfrage ein simples "1", was aber u.U. nur bedeutet, daß das Recordset Daten enthält.

Wenn über ein RecordSet Daten abgefragt werden sollen und man die Anzahl der gefundenen Daten wissen möchte, muß zuerst zum letzten Datensatz gesprungen werden, um anschließend über die vielversprechende Eigenschaft 'RecordCount' angezeigt zu bekommen, auf wieviele Datensätze zugegriffen wurde:

    RecordSetName.MoveLast
    Gesamt = RecordSetName.RecordCount

oder besser:

    If RS.NoMatch = False Then
       RS.MoveLast
    End If

Meistens folgt auf diese Zeilen der Demütigung ein RS.MoveFirst, da man ja manchmal mit den Daten, die man so mühsam herausgefischt hat, auch arbeiten möchte.

Die Eigenschaft RecordCount nimmt folgende Werte an:

-1  bei nicht eingebundenen Tabellen.
 0  bei leeren Tabellen.
>0  bei Tabellen, die Datensätze enthalten.

Wenn ein Recordset frisch geöffnet wurde ( DB.OpenRecordset ), dann gibt RecordCount die Werte -1, 0 und 1 zurück, entsprechend der oben angeführten Bedeutungen.

Dies ist auch dann der Fall, wenn der Parameter DB_OPEN_SNAPSHOT der Methode OpenRecordset zugefügt wurde ( dann darf aber kein RS.MoveLast erfolgen ! ). Eigenartig wird es, wenn man auf der ewigen Suche nach Geschwindigkeit den
Versuchungen des weiteren Parameters DB_FORWARDONLY erliegt. Der gibt im Gegensatz zu obiger Ausführung auch bei nicht-eingebundenen Tabellen -1 zurück, wenn das Recordset keine Daten enthält ( Selbstverständlich darf auch hier kein
MoveIrgendwas erfolgen ).


Bookmark   Quelle: dmt   Datum: 03.2004   nach oben

BOOKMARK / FORM_OPEN / OPENARGS / Datensätze synchronisieren:

Obwohl 'Zeige_Stammdaten' den Aufruf von Pflege-Formularen sowie Setzen des aktuellen Datensatzes gut abcheckt, ist es manchmal vonnöten, das im Form_Open des Formulares zu erledigen. In dem gezeigten Beispiel wird sogar eine
vollständige Where-Klausel übergeben und ausgewertet.

    If Not IsNull(Me.OpenArgs) Then
       Me.RecordSetClone.FindFirst Me.OpenArgs
       Me.Bookmark = Me.RecordSetClone.Bookmark
    End If


DLookup, Tücken   Quelle: dmt   Datum: 03.2004   nach oben

Mit DLOOKUP kann auch mal was schiefgehen:

Wird in der Where-Klausel per Tippfehler z.B. ein ungültiger Feldname eingetragen, wird kein Fehler erzeugt, sondern DLookup spuckt völlig unschuldig den Feldwert des ersten Datensatzes aus, so als ob überhaupt keine Klausel eingegeben worden wäre - lächerlich !


Literale   Quelle: dmt   Datum: 03.2004   nach oben

LITERALE oder 'der Versuch, Daten in eine Access-Tabelle einzugeben'.

Bei nicht editierten Feldern wird ein vorgesehenes Literal nicht in der Tabelle gespeichert, obwohl die Option zur Abspeicherung der Literale explizit gesetzt wurde, und ist somit auch als Teil des Feldinhaltes nicht auswertbar. Dies
geschieht selbst dann nicht, wenn für dieses Feld Standardwerte vorgesehen sind. Diese werden auch bei Nicht-Editierung gespeichert ... nur das Literal nicht.

Erst wenn z.B. ein schon vorhandenes Zeichen, wie ein Literal-Strich mit sich selbst überschrieben wird, wird das Literal als Teil des (evtl. immer noch leeren) Feldinhaltes gespeichert und ist dann später auch auswertbar.

So ein Dreck !!

Im Rahmen der vielen Details des AT/VHP-Managers gibt es die Sache mit dem 'Formel-bereich', der aus einer Reihe von verschiedenen Textfeldern besteht, deren Inhaltssumme einen festen Betrag (60 Zeichen) aufweisen muß. Auch hier mußte festgestellt werden, daß die Sache mit Eingabemasken, die Literale enthält, die auch gespeichert werden sollen, nicht hinhaut, ganz zu schweigen von einfachsten Tastaturoperationen wie <Entf>, <Backspace> etc.. Zu einer selbstgestrickten Lösung mit eigenen 'Füllzeichen' gibt es in hp_vhp.txt folgende Notizen:

Alle Formelfelder werden neben den bereits bekannten default-Werten mit einem Platzhalterzeichen '·' aufgefüllt. Damit ist gewährleistet, daß die Summe aller Feldinhalte des Formelbereichs den geforderten 60 Zeichen ent- spricht. Hier hat sich jedoch herausgestellt, daß Access den Feldinhalt unter Umständen nicht vollständig speichert. Deswegen wurde eine Routine entwickelt, die beim Anlegen eines neuen Datensatzes die Literale enthaltenden Felder im Formelbereich mit künstlichen Einträgen versieht, damit auch immer die Summe von 60 geforderten Zeichen gewährleistet ist.

Eine Haken hat die Sache allerdings: Wenn die Vorbereitungen für diesen Datensatz mit <Esc> rückgängig gemacht werden, kann es bei unvollständigen Angaben im Formelbereich dazu kommen, daß die 'Prüfung vor Speichern' ungültige Länge des Formelbereiches meldet !

Das sollte sich zur Not auch noch beheben lassen.

Ein Check bei Form_BeforeUpdate prüft zur Sicherheit z.Bsp., ob ungültige Werte in die Felder kopiert werden und ob die Summe = 60 ist.

Durch spezielle Tastatur-Abfang-Routinen ist eine Verletzung der Formel-struktur fast unmöglich gemacht worden:
Die Editier-Tasten <Entf>, <Leer> und <Rückschritt>, die normalerweise vorhandene Zeichen durch Leerzeichen ersetzen, überschreiben in den Formel-Feldern vorhandene Zeichen mit dem Sonderzeichen '·'. Ein Problem
tritt aber mit den Editier-Tasten <Entf> und <Rückschritt> auf. Access erzeugt trotz Abfangen dieser Tasten und Einsetzen des gewünschten Sonderzeichens ungültige Feldeinträge, die sich mit der vorgesehenen Eingabemaske beißt.

Die Frage ist, ob sich das nicht auch mit exzessiven Tastatur-Umbiege-Routinen noch idiotensicherer machen ließe.


Recordnumber   Quelle: dmt   Datum: 03.2004   nach oben

Das Ultra-Scheiß-Thema RECORDNUMBER (Datensatznummer):

Schweiß und Tränen, aber es wurde wahr; auch wenn die Entwickler von MS-ACCESS nicht imstande waren, ihrer Datenbank eine recno-Variable mit auf den Weg zu geben, obwohl ein entsprechender Wert in den Navigationsleisten ständig angezeigt wird, konnte im Hause Breitenbücher eine Lösung gefunden werden, die diesen leidigen Umstand fast vergessen läßt.

Allgemeines Beispiel: In einer Liste, in der ein bestimmter Datensatz der aktuelle ist, wird eine Manipulation durchgeführt, die den Datensatzzeiger z.Bsp. auf den Listenanfang stellt. Im besten Fall langt eine einfache, temporäre
Übergabe des BOOKMARK-Wertes an eine Variable und eine spätere Rückgabe an das RecordSetClone der realen, manipulierten Liste. Normaler weise ist aber alles ein bißchen komplizierter, also hier 'the real thing':

Dim Recno As Integer            ' Diese Variablen müssen im Deklarationsteil
Dim Datensatz As String         ' definiert werden, damit sie in allen
Dim Aktives_Feld As Control     ' Prozeduren des Modul zur Verfügung
Dim Datenabfrage As Recordset   ' stehen.

Datensatz = Forms![Auftrag]![Auftrag_UF].Form.Bookmark  ' hier wird das Lesezeichen des aktuellen Datensatzes  an einen String übergeben.
Set Aktives_Feld = Screen.ActiveControl                 ' selbst zu dem gerade aktuellen Element kann später zurückgekehrt werden.
Set Datenabfrage = Forms![Auftrag]![Auftrag_UF].Form.RecordSetClone
                                                        ' Ein Clone der Liste wird an die RecordSet-Variable übergeben.

Datenabfrage.Bookmark = Datensatz                       ' Der defaultmäßig auf 1. stehende Clone-Zeiger wird zum Lesezeichen bewegt.

Recno = 0                                               ' Recno solange addieren,
While Not Datenabfrage.BOF                              ' bis der Datensatzzeiger im Clone unter
      Datenabfrage.MovePrevious                         ' den ersten, gültigen gerutscht ist.
      Recno = Recno + 1                                 ' Recno hat den Wert der nicht vorhandenen DS-Nummer angenommen !
Wend

Forms![Auftrag]![Summe_Netto].Requery                   ' Beispiel für eine Manipulation, die den aktuellen Datensatz verändert.

Aktives_Feld.SetFocus                                   ' Hier wird schon mal das richtige Element angesteuert.

Set Datenabfrage = Forms![Auftrag]![Auftrag_UF].Form.RecordSetClone
                                                        ' Die RecordSet-Variable wird zum Clone der manipulierten Liste.
While Recno > 0                                         ' Die 'Datensatznummer'-Variable Recno wird
      Datenabfrage.MoveNext                             ' solange erniedrigt, bis sie den Wert 0 erreicht hat.
      Recno = Recno - 1                                 ' Gleichzeitig wird der Zeiger der Clone-Liste um je
Wend                                                    ' einen Schritt nach vorne bewegt.

Datensatz = Datenabfrage.Bookmark                       ' Hier wird das Lesezeichen  des virtuellen, richtig stehenden Datensatz-Zeigers an die Variable übergeben.
Forms![Auftrag]![Auftrag_UF].Form.Bookmark = Datensatz  ' Das ist Rückgabe des Lesezeichens vom Clone an die manipulierte Liste.
SendKeys "{ENTER}"                                      ' Schmankerl zum Schluß.

Das künstliche ENTER springt zu dem Element, das angesprungen hätte werden sollen.

Diese tolle und überaus naheliegende Vorgehensweise wird erst dadurch ermöglicht, daß die Lesezeichen (Bookmarks), die äußerst undurchsicht vergeben werden, zwischen einer realen Liste und deren Clone identisch sind.

Die umständliche Hin- und Herübergeberei von Bookmarks an Stringvariablen und umgekehrt ist nicht unbedingt nötig, es trat dann aber, kurz nachdem diese Zeile geschrieben wurde, prompt der Fall ein, in dem eine derartige Übergabe für eine
10.tel-Sekunde ausgeführt wurde, dann aber aus unerfindlichen Gründen wieder der default-Datensatz des Formular-Recordsets dargestellt wurde.

Ob so etwas wie folgt bzw. in ähnlicher Form ohne temp-Variable gut geht, muß jeweils geprüft werden !

Clone.Bookmark=Org.Bookmark
    ...
Org.Bookmark=Clone.Bookmark

Abschließend kann ich nur sagen:So ein Dreck !

Und, falls es irgendjemanden interessieren sollte: Wenn kein gültiger Datensatz gefunden wurde, enthält die Eigenschaft Bookmark eine nicht darstellbare Stringzeichenfolge mit asc(0) !


Sortierung   Quelle: dmt   Datum: 03.2004   nach oben

SORTIERUNG / REIHENFOLGE DER DATENSÄTZE in TABELLEN, RECORDSETS ( Formulare und Abfragen) / BERICHTE / Öffnen einer Tabelle im Datenbankfenster oder per Code:

Ein ganz, ganz trauriges Kapitel muß hier aufgeschlagen werden:

In Kürze:

Sortierung von Daten in Berichten läuft NICHT über ORDER BY - SQL - Statements, sondern über die berichtseigene Gruppierungsgeschichte. s. Sortierung und Gruppierung von Berichtsdaten per Quelltext

Gegeben sei der Fall, daß eine Tabelle 'Termine', in der lediglich das Datumsfeld 'von' indiziert (ohne Duplikate) ist, beim Öffnen ohne Sortier-Tricks die Daten in der sinnvollen Reihenfolge des chronologischen Ablauf darstellt. Das heißt, daß auch Termine die später eingegeben und mit einem sehr alten Datum versehen werden, beim erneuten Öffnen der Tabelle richtig einsortiert werden. Die Tabelle enthält zwar auch ein Zählerfeld, das aber nur zur Steuerung einer
Untertabelle dient und nicht indiziert ist.

Wird eine derart eingerichtete Tabelle als Datenherkunft für eine Abfrage oder ein Formular gewählt, erscheinen die Daten dort dann ebenfalls in der gewünschten Reihenfolge, und das sehr schnell, ohne daß ein ORDER-Kriterium angegeben werden muß. Lediglich das Einfügen eines WHERE-Kriteriums macht die 'natürliche' Sortierung zunichte und erfordert das Einfügen eines ORDER-Kriteriums.

Das oben beschriebene herauszufinden hat schon einiges an Zeit und Nerven gekostet, aber das folgende setzt dem noch die Krone auf:

Wird eine Tabelle über DB.OpenRecordset ("Termine", DB_OPEN_TABLE) geöffnet, sollte man meinen, die Daten liegen in der 'natürlichen' Sortier-Reihenfolge vor. Fehlanzeige !

Man wird erst nach längerer Zeit mit stundenlanger Suche auf geisterhafte Fehlzuordnungen etc. stoßen, um irgendwann einmal festzustellen, daß beim Öffnen einer Tabelle per Code eine andere Reihenfolge der enthaltenen Daten vorliegt, als beim Öffnen dieser Tabelle im Datenbankfenster. Hier scheint dann überhaupt GAR KEIN Konzept mehr vorzuliegen: Ein Termine-'out'-Datensatz mit dem Zähler-Ident 2628 und der chronologischen Position 286 wird als 228.ter Datensatz in der RecordSet-Tabelle gefunden.

Die in einer per OpenRecordset geöffneten Tabellen-Daten lassen sich in ihrer nicht nach-vollziehbaren Reihenfolge mit keiner anderen Methode visualisieren, als mit Hilfe einer DMT-typischen Standard-MsgBox in einer Schleife, die alle Datensätze des Recordset-Objektes durchnudelt.

Das oben beschriebene Problem kann nach derzeitigem Wissensstand nur dadurch gelöst werden, daß das Recordset-Objekt sich nicht auf die Tabelle selbst, sondern auf einen SQL-String stützt, der einen entsprechenden ORDER-Teil enthält.

Meine spöttische Aussage, man müsse froh sein, wenn die Daten, die heute eingegeben werden, morgen noch da sind, bewahrheitet sich wieder einmal mehr.

Beim Drucken von Berichten kommt es im Zusammenhang mit dem Thema 'Gruppieren und Sortieren' vor, daß Daten in einer anderen Reihenfolge als in der zugrundeliegenden Tabelle, oder noch besser, der zugrundeliegenden Abfrage, die selbst eine 'ORDER BY'-Anweisung enthält, ausgedruckt werden.

In einem Fall wurde ein Kopf- und Fuß-loses Feld als Sortierkriterium und ein zweites mit Gruppenfuß zur Gruppierung angegeben. Die Sortierung war nicht nur eine falsche, sondern eine MANCHMAL falsche. Eine für das Feld 'Nr' bestimmte Gruppierung wurde dann jedesmal für das Sortierkriterium 'Datum' ausgelöst, und das auch noch mit wahlweise zerstörter Sortierung oder auch nicht ! Stundenlanges Doku-Lesen und Herumprobieren brachte nix. Erst wiederholtes Nachlesen in meiner eigenen Kotz-Sammlung (dieser hier) ließ mich auf den Hinweis stoßen, daß das erste Kriterium im Fenster Sortieren und Gruppieren' z.Bsp. für eine Gruppierung verwendet werden kann, und erst ein folgendes Kriterium zur Sortierung.

Im Klartext:

Wenn Daten innerhalb eines Berichtes sortiert und gruppiert ausgedruckt werden sollen, dann müssen zuerst die Gruppenkriterien und dann die Sortierkriterien (soll das logisch sein oder was ?) angegeben werden.

Zum Thema Reihenfolge der Daten in einem Formular kann aber doch etwas positives gesagt werden:

Das vom Anwender veränderbare Erscheinungsbild der Daten eines Formulares ist wenigstens zur Laufzeit im Quelltext durch die Eigenschaft RecordSetClone greifbar.
s.a. sortierte Daten in Formularen mit Berichten drucken

* * * *

Sortierung und Gruppierung von Berichtsdaten per Quelltext:

Beispiel zu den Gruppeneigenschaften (MS-Access-Doku):

Dieses Beispiel legt die Sortierreihenfolge- und Gruppen-Eigenschaften für die erste Gruppenebene in TestBericht fest.

' Name des Feldes, nachdem gruppiert bzw. sortiert werden soll.
Reports![TestBericht1].GroupLevel(0).ControlSource = [Feldname]
' Eigenschaft "Sortierreihenfolge" (SortOrder) auf absteigend
' einstellen.
Reports![TestBericht1].GroupLevel(0).SortOrder = -1
' Eigenschaft "GruppierenNach" (GroupOn) auf "Anfangszeichen"
' einstellen.
Reports![TestBericht1].GroupLevel(0).GroupOn = 1
' Eigenschaft "Intervall" (GroupInterval) auf 1 einstellen.
Reports![TestBericht1].GroupLevel(0).GroupInterval = 1
' Eigenschaft "Zusammenhalten" (KeepTogether) auf
' "Mit 1. Detaildatensatz" einstellen.
Reports![TestBericht1].GroupLevel(0).KeepTogether = 2

ABER:

Die Eigenschaft GroupOn kann per Quelltext nicht gesetzt werden und
ControlSource kann nicht auf "" eingestellt werden.

* * * *

MEHRFELDER-INDIZES / INDEX:

Mal wieder was positives:

Das seit langem brachliegende Thema der Mehrfach-Indizes (die Clustered-Eigenschaft wird laut Doku nicht unterstützt) konnte in einer motivierten 5-min-Session sinnvoll aufgearbeitet werden.

Um z.B. in einer Zuordnungstabelle, die Idents und Schlagworte enthält, sicherzustellen, daß keine Mehrfelder-Dubletten entstehen, mußten bisher Formular-Code-basierende Routinen bemüht werden.

Genau das kann aber über einen Mehrfelderindex auf Tabellenebene gelöst werden. Obendrein werden die Daten in der Tabelle auch noch in der absolut richtigen Sortierreihenfolge dargestellt (nach Ident und dann nach Schlagwort).
Und das geht so:

- Im Entwurfsfenster den Index-Dialog öffnen
- Einen Index benennen und das erste Feld angeben
- in der zweiten Zeile ohne Indexnamen ein weiteres Feld angeben (bis zu 10!)

Der erste Eintrag erhält die Eigenschaft Eindeutig=ja.

Das Speichern mißlingt natürlich, wenn Datensätze Mehrfelder-Dubletten enthalten. Dafür gibt es aber beim Thema Duplikatabfrage reichlich Hilfe und Lösungen.

Wird allerdings gewünscht, daß die Reihenfolge der 1:n-gelisteten Daten keine sortierte sein soll, wird, muß man sich wieder auf den Ident-Einfeld-Index beschränken und Code-Lösungen zur Vermeidung der Dublettenvergabe einsetzen.

Es kann u.U. vorkommen, daß sich neben einem 'Primary Key' auch noch ein normaler Index einschmuggelt. Bei wichtigen Tabellen ruhig einmal in der Index-Liste nachschauen.


Suche-Formular   Quelle: dmt   Datum: 03.2004   nach oben

SUCHEN von DATENSÄTZEN in FORMULAREN / SUCHE-FORMULARE:

Klar, wenn man einem halbwegs interessierten Anwender die Grundlagen logischer Aussagen (Anwender bevorzugt männlich) sowie die Bedienung des Access-eigenen Filter-/Sortierdialoges näherbringt, ist das Problem 'Suche nach Datensätzen'
eigentlich gelöst.

Bei einem Programm, das auch wirklich benutzt wird, wird aber auch häufig gewünscht, Standard-Suchanfragen über einen einfach zu bedienenden Dialog ausführen zu können.

Das wird ziemlich schnell ziemlich aufwendig, da so etwas erst dann Spaß macht, wenn z.B. aufgeräumte Kombinationsfelder die Liste verfügbarer Feldnamen kennen usw.

Erste, interessante Ansätze gab es bereits im HP-VHP-Manager.
Halbwegs gelungene Lösungen sind in LITSTG.MDB und NB21.MDB zu finden.

Hier ein paar Details:

Diverse Subroutinen erstellen den SQL-Where-String, sogar ein Parser, der deutsche Angaben in SQL-Englisch übersetzt ist möglich (LITSTG, aber Vorsicht!, so was wird NIE fehlerfrei). Für Überinteressierte kann auch der gebildete SQL-String in einem Manual-Checkformular angezeigt und verarbeitet werden. Sogar das Speichern und Auswählen/Pflege von Suche-Datensätzen ist möglich (LITSTG, tut sogar).

Interessant ist die neueste Technik des Aufrufes des Daten-Formulares:

Toll ist es erst dann, wenn ein einzelner 'gefundener Datensatz' in der Formularansicht und mehrere als Liste dargestellt werden und das ganze obendrein auch nicht flackert. Die präsentierte Methode beherrscht diese Fälle, ist fehlertolerant (nichts ist übler als ein auftretender Fehler nach einem mutigen Application.Echo False) und wird durch dieses Verfahren auch noch deutlich schneller.

    ' **** Bildschirmflackern und Darstellungswechsel elegant unterdrücken ****
    ' **** bringt nebenbei auch noch Geschwindigkeit, yes !                ****

    iShutUp = True
    Application.Echo False, "Suche läuft ..."

    DoCmd OpenForm sForm, A_FORMDS, , sKrit

    iAnzahl = Forms(sForm).RecordsetClone.RecordCount

    Select Case iAnzahl
        Case 0: DoCmd Close A_FORM, sForm
                Beep
                MsgBox "Es konnten leider keine Datensätze gefunden werden, die Ihren Angaben entsprechen.", 64, "Suche"
        Case 1: DoCmd RunMacro "Menübefehle.AnsichtFormular"
    End Select

    Application.Echo True

    Exit Sub


err_Suche:

    Fehler "Suche"

    If iShutUp = True Then
       Application.Echo True
    End If

    Exit Sub


suchen   Quelle: dmt   Datum: 05.2006   nach oben

SUCHEN VON DATENSÄTZEN / FIND-Methoden / FINDRECORD / FINDFIRST / SEEK:

Um z.Bsp. innerhalb eines geöffneten Formulares zu einem gewünschten Datensatz zu springen, kann alternativ zur Bookmark-Strafe (Arbeiten mit Recordsetclone, Rückübergabe etc.) auch FindRecord eingesetzt werden. Dies entspricht übrigens genau dem Access-Suche-Fenster, daß per Symbolleiste oder mit <Strg+f> aufgerufen werden kann.


Positionierung des Treffers:

Obendrein kann es von Interesse sein, wo der Treffer-Datensatz in einer Trefferliste zu sehen ist.

Mal sollte er ganz oben stehen:

    sBM = Me.Bookmark
    RS.MoveLast
    Me.Bookmark = RS.Bookmark
    Me.Bookmark = sBM

oder in einem alphabetischen Kontext:

    DoCmd FindRecord Left(Me!Treffer.Column(1), 2)
    DoCmd FindRecord Me!Treffer.Column(1)

Typischer Anwendungsfall:

In einem Listen-Unterformular soll auf ein Kombinationsfeld Datensatz doppelgeklickt werden können, um dadurch das entsprechende Formular mit dem gewähltem Datensatz zu öffnen. Alternativ hierzu besteht für Tastatur-Freaks die Möglichkeit, dies z.Bsp. mit <Strg+Leer> abzuchecken. Obendrein wird das 'Mutterformular' ausgeblendet, um optischen Wildwuchs zu verhindern. Beim Schließen des aufgerufenen Formulares kann das Mutterformular, wenn es in der Access-internen Liste geöffneter Formulare enthalten ist, wieder dargestellt werden.

Sub Stichwort_DblClick (Cancel As Integer)

    DoCmd OpenForm "Stichworte", A_NORMAL, , , , , Me!Stichwort.Column(1)
    Forms!Wissen.Visible = False

End Sub


Sub Stichwort_KeyDown (KeyCode As Integer, Shift As Integer)

    If KeyCode = 32 And Shift = 2 Then
       KeyCode = 0
       Stichwort_DblClick (0)
    End If

End Sub


Sub Form_Open (Cancel As Integer)

    If Not IsNull(Me.OpenArgs) Then
       Me!Titel.SetFocus
       DoCmd FindRecord Me.OpenArgs
    End If

End Sub


Sub Form_Close ()

    If ExistsForm("Wissen") Then
       Forms!Wissen.Visible = True
    End If

End Sub

Für den Fall, daß als Merkmal z.B. ein ID-Feld benutzt wurde, das als Steuerelement im Formular selbst gar nicht enthalten ist, kann FindRecord nicht benutzt werden, da das Steuerelement zur Suche nicht fokussiert werden kann. Diesem Problem kann jedoch mit einer schnuckeligen Lösung zu Leibe grückt werden:

    If Not IsNull(Me.OpenArgs) Then

       Me.RecordSetClone.FindFirst "ID=" & Me.OpenArgs
       Me.Bookmark = Me.RecordSetClone.Bookmark

    End If

Geschwindigkeit:

FindRecord (a'la <Strg+F4>-Access-Suche-Fenster) ist sehr schnell, wenn es auf Überein-stimmung mit dem gesamten Feldinhalt und auf Suche im aktuellen Feld eingestellt ist.

Die bedingbaren Find-Methoden für Recordsets sind wie die bedingungslosen Move-Methoden in relativer Umgebung auch sehr schnell. Für ein Feld, das in der Tabelle nicht als Index definiert wurde, kann aber ein

'RS.FindFirst "Feld=" & Wert'
vom ersten auf den letzten von ca. 3.000 Datensätzen bei einem 486 DX4 schon fast 1 Sekunde dauern; zu lange, um z.B. in Form_Current eingesetzt werden zu können.

Eine Alternative hierzu kann aber die Suche per SEEK sein.

Das geht aber nur, wenn das Recordset-Objekt per DB_OPEN_TABLE vom Typ Tabelle ist und macht erst dann richtig Sinn, wenn das zu bestimmende Feld als Index definiert wurde.

Eine Datensatzgruppe von 25.000 Datensätzen, die per Schleife mit checkendem Code als Dynaset durchlaufen wird, genehmigt sich ca. 35s, davon allein 16s zum Öffnen der Datenquelle.

Als 'indizierte Feld / Seek'-Geschichte geht das in ca. 6s, das Anspringen bestimmter Datensätze spielt sich im 1/10s-Bereich ab. Seht gut geeignet für Schleifen-Vergleichs-Nudeleien wie sie (leider) auch schon des öfteren in den
guten, alten OA3-Zeiten vorkamen.

Bleibt zu klären, wie sehr es zu Beeinträchtigungen kommt, wenn das betreffende Feld nicht indiziert wurde; auf jeden Fall können nur direkte Vergleiche mit dem Feldinhalt angestellt und keine mehrteiligen Ausdrücke a'la SQL angegeben
werden.

Ein Programmierer beklagte sich mir gegenüber einmal über zu lange Suchzeiten bei Anwendung der Methode Seek. Ich war damit zufrieden.
Im weiter oben angeführten Beispiel habe ich auch Seek getestet, und, obwohl ich nicht das in diesem Falle nicht indizierte Feld 'ID' benutzen konnte, sondern ein indiziertes Datumsfeld belangen mußte, wurde die Aktion UNABHÄNGIG von der aktuellen Datensatzposition trotz einer Reihe notwendiger Code-Zeilen in ca. 1 Zehntelsekunde ausgeführt.

Die einzigen Nachteile von Seek sehe ich im nötigen Code:

Set DB = DBEngine.Workspaces(0).Databases(0)
Set RS = DB.OpenRecordset("Termine", DB_OPEN_TABLE)
RS.Index = "von"
RS.Seek "=", RS.von

und der Tatsache, daß eine Seek-Suche nur in einem in der Tabelle indiziertem Feld durchgeführt werden kann.

Wenn also in einer großen Tabelle bestimmte Werte angesprungen und mit anderen in dieser Gruppe verglichen werden sollen, ist Seek sehr schnell.

Wenn aus einer großen Tabelle ein Wert unter einer Bedingung ermittelt werden soll, ist nach wie vor DLookup einfach und schnell, vor allem, wenn ein indiziertes Feld abgefragt wird.

Als beinahe gelungen kann folgende Lösung bezeichnet werden (Formular öffnen / gewünschten Datensatz einstellen):

Hier wird zuerst der gesuchte Datensatz angesprungen (ähnlich wie im Termine-Formular in dmt.mdb). Da aber bei größeren Datenmengen der aktuelle Datensatz oft am unteren Ende des sichtbaren Listenteil steht, muß der Anwender nach unten scrollen um die folgenden unerledigten Daten zu sehen. Der zweite Teil der Routine macht das automatisch. Er springt an das Ende der Liste und wieder zurück zum eigentlichen Wunschdatensatz.

Einsatz in Form_Open.


    On Error GoTo err_Form_Open_Aktionen

    Application.Echo False

    Dim RS As Recordset, sBM As String

    Set RS = Me.RecordsetClone

    RS.FindFirst "erledigt=false"

    If RS.NoMatch Then
       RS.FindFirst "Datum>=Date()"
    End If

    Me.Bookmark = RS.Bookmark

    ' **** Ok, der gewünschte Datensatz ist markiert, aber er steht   ****
    ' **** irgendwo in der Liste. Besser wäre es, wenn er der oberste ****
    ' **** im sichtbaren Listenteil wäre.                             ****

    sBM = Me.Bookmark
    RS.MoveLast
    Me.Bookmark = RS.Bookmark
    Me.Bookmark = sBM


exit_Form_Open_Aktionen:

    Application.Echo True

    Exit Sub


err_Form_Open_Aktionen:

    Fehler "Form_Open_Aktionen"
    Resume exit_Form_Open_Aktionen

Manchmal muß man so etwas mit einer Tabelle anstellen, und dann hagelt es dann auch gleich wieder Probleme:

Es gibt keinen Suchbefehl, der SQL-Kriterien in einer auf dem Desktop geöffneten Access-Tabelle entgegennimmt, obendrein müssen bei Zuordnungstabellen sogar zwei Kriterien abgecheckt werden. Ist das erste Feld relevant, kann mit dem <Strg+F>-Äquivalent FindRecord gearbeitet werden, aber wie setze ich den Focus auf ein anderes Feld der Tabelle?

Fragen über Fragen, und wenn man mir genügend Zeit und Ruhe läßt, gibt's auch auf so etwas Antworten:

Sub Personen_DblClick (Cancel As Integer)

    On Error GoTo err_Personen_DblClick

    Dim s As String

    s = "Personen_Zuordnungen"

    Meldung "synchronisiere Daten ..."
    DoCmd OpenTable s
    DoCmd FindRecord Me!Ident
    Meldung ""

    DoCmd SelectObject A_TABLE, s
    DoCmd GoToControl "Personen"
    DoCmd FindRecord Me!Personen, , , , , , False

    Exit Sub


err_Personen_DblClick:

    Fehler "Personen_DblClick"
    Exit Sub

End Sub

So, ist fast ein bißchen groß geworden, hat aber einen richtigen Fehlerhandler und kann was.
Es wird die Tabelle geöffnet und der erste Suchvergleich für das hier erste Feld durchgeführt. Nach dem Ausschalten der Meldung muß das Tabellenobjekt wieder fokussiert werden, dann kann aber auch der Fokus per GotoControl verschoben werden, um eine erneute Suche durchzuführen. Die zweite Suche wird per Parameter erst ab dem auf den aktuellen folgenden Datensatz durchgeführt, in der Praxis klappt das aber auch, wenn beim ersten Durchlauf bereits der richtige gefunden wurde.

Interessanterweise kann nach einem SelectObject auch ein GotoControl zum gewünschten Feld ausgeführt werden, obwohl es selbst gar kein Steuerelement ist !!!

* * * *

UMLAUTE, SONDERZEICHEN und andere Tabu's:

Eine wirksame Schikane ist auch der Umstand, daß ein Tabellen-Feld 'PRIVATNR' über ein Basic-Recordset zwar sowohl per 'RS.Privatnummer', 'RS("PRIVATNR")' oder auch 'RS("Privatnr")' angesprochen werden kann, aber im Falle von 'GESCHÄFT' der Bezug mit 'RS("Geschäft")' zu der Meldung 'Name in der Sammlung nicht gefunden' führt. Hier verhält sich die Sache mimosenhaft Case-sensitiv, ebenso klappt weder ein 'RS.Geschäft' noch ein 'RS.GESCHÄFT'. Was lernen wir daraus ?
Eben mit Namen so vorsichtig wie möglich umgehen; wenn's geht, alle Konventionen der frühen 70er einhalten und nach Möglichkeit neue Features meiden wie die Pest.

Fast noch besser ist die Umwandlung eines Strings in einen Zahlenwert, die z.B. bei Val("40458 Dusseldorf") den richtigen Wert 40458 ergibt. Bei der einen Umlaut enthaltenden Schreibweise Val("40458 Düsseldorf") erhält man den geilen Wert 4,0458E+208. Ob das der Postcomputer mitmacht ?

Eine ebenfalls sehr schöne Schikane ist der Vergleich von Zeichenketten, die ein 'ß' enthalten:

Access behauptet doch allen Ernstes, daß "Anschluss 1" identisch mit "Anschluß 3" ist.
Weder interessiert, daß die Anzahl der Zeichen beim 'ss'-String um eins größer ist, noch daß nach dem folgenden Leerzeichen eine andere Ziffer steht. Weitere Manipulationen an den String's, um den Unterschied zu vergrößern, werden dann wieder erkannt. Auch daraus können wir nur lernen, daß man sich bei der Benennung von Objekten aller Art in MS-ACCESS an die Konventionen halten sollte, die bereits zu Zeiten eines 8088ers galten.

Einen Höhepunkt durfte dieser Umstand in der SEL-Klumpp-LITSTG Datenbank erreichen. In einem Klappfeld eines Unterformulares wird eine Distinct-Personen-Liste geführt. Dort sind u.a. alphabetisch verteilt Namen wie 'Rösner', 'Roßnagel', 'Roessner' und 'Rössner' vertreten. Beim Eintippen in das Klappfeld (aut. ergänzend) tritt beim zweiten 's' nach 'Rös' regelmäßig eine Schutzverletzung auf, die dann Access anschließend zwangsläufig beendet.


Unique   Quelle: dmt   Datum: 03.2004   nach oben

Unique Values / Eindeutige Werte:

Vereinzelt kommt es vor, daß Werte zur (eigentlich) eindeutigen Identifizierung eines Datensatzes mehrfach leer sein können.

Dies verträgt sich nicht mit dem Konzept eines Unique-Index.

Auf der Ebene der Tabellen-Eigenschaften können solche Zustände so durchgesetzt werden:
- Eingabe erforderlich: nein
- Leere Zeichenfolge:ja
- Indiziert: ja (ohne Duplikate)

Der Anwender erfährt von einer echten Verletzung (Speichern eines bereits vergebenen Wertes) erst, wenn der Datensatz gespeichert wird, und das ohne Nennung des Feldnamens.

Eine Code-Lösung, die leere Werte zuläßt und die den Wert der Variablen Cancel des Ereignisses BeforeUpdate steuert sieht aus wie folgt:

Bsp:

Sub BNr_BeforeUpdate (Cancel As Integer)

    Cancel = CheckUniqueValue(Me!BNr, "ventile", True, "'")

End Sub

und hier die Funktion:

Function CheckUniqueValue (C As Control, sTabelle As String, NullAllowed As Integer, sDelimiter As String) As Integer

    On Error GoTo err_CheckUniqueValue

    Dim iCancel As Integer

    ' **** Der Rückgabewert ist das "Cancel" des Ereignisses BeforeUpdate ! ****

    If IsNull(C.Value) Then
       If NullAllowed = True Then
          iCancel = False
          GoSub exit_CheckUniqueValue
       Else
          Beep
          iCancel = True
          MsgBox "Für das Feld '" & C.Name & "' sind leere Werte nicht zulässig !", 16, "CheckUniqueValue"
       End If
    End If

    If IsNull(DLookup(C.Name, sTabelle, C.Name & "=" & sDelimiter & C.Value & sDelimiter)) Then
       iCancel = False
    Else
       If Not IsNull(C.OldValue) And C.Value = C.OldValue Then
          iCancel = False
       Else
          Beep
          iCancel = True
          MsgBox "Der Wert '" & C.Value & "' wurde für das Feld '" & C.Name & "' bereits vergeben !", 16,  CheckUniqueValue"
       End If
    End If


exit_CheckUniqueValue:

    CheckUniqueValue = iCancel

    Exit Function


err_CheckUniqueValue:

    Fehler "CheckUniqueValue"
    Exit Function

End Function


Zähler   Quelle: dmt   Datum: 06.2004   nach oben

ZÄHLER:

Ein in einer Tabelle angelegtes Zählerfeld (z.B. Auftragsnummer) verhält sich in der Praxis reichlich merkwürdig. Wird eine Neueingabe gestartet, enthält das Feld den sehr aussagekräftigen String '(Zähler)'. Nach dem Eingeben eines Zeichens erhält das Feld dann einen Wert. Doch dieser ist keineswegs der Wert der nächst höheren Nummer anhand der bereits vergebenen Nummern. ACCESS behält sich das Recht vor, diesen Wert bei jedem Eingabe-Start hochzusetzen, auch wenn die Eingabe mit diversen <Esc>'s abgebrochen wurde und eine Speichern des Datensatzes überhaupt gar nicht stattgefunden hat. Selbst das Beenden der Software bringt nichts, da hier ein interner Wert DAUERHAFT gesetzt wird. Erst ein 'Komprimieren' der Datenbank setzt diesen Dreck's-Eintrag wieder zurück.

Aber auch hier konnte im Hause DMT eine Lösung gefunden werden:

1.Zähler-Felder werden ab jetzt nicht mehr benutzt, wenn diese eine für das
  menschliche Auge durchgehende Reihenfolge haben sollen.
  Zähler-Felder waren auch unter OA3 nicht ganz unproblematisch.
2.Stattdessen wird ein Zahlenfeld definiert (Integer langt meistens)
3.und das entsprechende Feld in dem Formular disabled und locked.
4.Die folgende, zum Formular gehörende Sub


Sub Form_BeforeInsert (Cancel As Integer)

   CheckZaehler "Auftrag", "Nr", "Auftrag"

End Sub

ruft nach dem ersten Tastendruck, der eine Eingabe in diesem Formular auslöst (BeforeInsert), folgende Sub auf:

Sub CheckZaehler (Fenster As String, Feld As String, Tabelle As String)

    On Error GoTo err_CheckZaehler

    ' Ermittle und übergebe den wahren Wert eines Pseudo-Zähler-Feldes.

    If Forms(Fenster).RecordSetClone.RecordCount = 0 Then  ' keine Daten
       Forms(Fenster)(Feld) = 1
    Else                                                   ' oder
       Forms(Fenster)(Feld) = DMax(Feld, Tabelle) + 1      ' erhöhe Zähler
    End If

    Exit Sub


err_CheckZaehler:

    Fehler "CheckZaehler"
    Exit Sub

End Sub

Unterschieden wird nach:
- noch ein Datensatz in der Tabelle
- und nicht der erste Datensatz.

Aus der Sicht des Anwenders verändert sich eher etwas zum Positiven, da der vielsagende Eintrag 'Auftragsnummer: [Zähler]' nicht mehr erscheint.

Ein anderer Lösungsansatz besteht in der Idee, eine Funktion zu schreiben, der Tabellen- und Feldname per Parameter übergeben werden und die mit der Auswertung einer einfachen DMax-Anweisung entsprechenden Zählerwerte zurückgibt. Leider ist es in Access nicht möglich, einen Funktionsaufruf für eine selbstentwickelte Funktion in der Eigenschaft "Standardwert" des Feldes im Tabellenentwurf unterzubringen. Als Standardwert für das Formularfeld geht das sehr wohl und tut auch großartig, aber leider nur in der Formularansicht. In der Datenblattansicht wird, während ein neuer Datensatz bearbeitet wird, der darauffolgende neue Datensatz mit dem Zählerwert des letzten angezeigt. Genauere Betrachtung geschweige denn Lösungsansätze und -strategien müssen erst einmal bezahlt werden. Solange hier nichts erregendes gefunden wird, gilt ein Verfahren wie das oben beschriebene als State-of-the-art.

Vielleicht kann sich auch in diesem Punkt das Blatt noch mal zum Gutem wenden. In den Ereignissen, die eine Veränderung am Datenbestand vornehmen (vielleicht reicht Form_-AfterUpdate), könnte eine kleine Routine aufgerufen werden, die per
DMax den nächst anstehenden Wert ermittelt und explizizt an die Eigenschaft Standardwert des Zähler-Imitat-Feldes zuweist.

Zu den Stichworten ZÄHLER und REIHENFOLGE gehört auch ein weiteres, trauriges Anwendungsproblem:

Z.B. in der Vergabe von Positionsnummern innerhalb einer Auftragsverwaltung ergibt sich der Umstand, daß die Positionsnummer innerhalb eines Auftrages eindeutig sein muß, obwohl sie in der Tabelle mehrfach vorkommen kann. Abgesehen von DefaultValue-Problemen, denen hier mit numerischen Werten in Feldern eines Unterformulares überhaupt nicht beizukommen war (beim 'Betreten' eines neuen Datensatzes wird per DMax knallhart ein neuer Pos-Wert zugewiesen), muß beim Speichern einer Auftragsposition beachtet werden, daß für diesen Auftrag keine andere Position mit derselben Nummer besteht. Normale Checkungen per DLookup stoßen leider aber auch beim Editieren eines bestehenden Datensatzes auf
denselben, der ja in der alten Form noch in der Tabelle steht und spucken großkotzig die Meldung aus, daß der Positionsdatensatz nicht angelegt werden kann, da bereits ein Datensatz zu diesem Auftrag mit dieser Positionsnummer besteht. Im Laserjob-Manager konnte der Sache nur durch Einfügen eines neuen, ansonsten unnötigen Zähler-Feldes beigekommen werden. Der entsprechende DLookup-Code sieht wie folgt aus:

    v = DLookup("ID", "Auftragspositionen", "Z=" & Me!Z & " AND Pos=" & Me!Pos & " AND ID<>" & Me!ID)

nach oben
zur Startseite dieses Webangebotes zur infobase-Hauptseite