infobase: EDV - MS-Access
Tabellen
Beziehungen
Quelle: dmt
Datum: 03.2004
BEZIEHUNGEN:
sind wie im wirklichen Leben auch in MS-ACCESS nicht immer ein Grund zur Freude.
Abgesehen davon, daß die Segnungen der referentiellen Integrität auch schon mal des Öfteren an mir vorbeigegangen sind, kann das Arbeiten mit Beziehungen zu einem mittleren Desaster führen, wenn später eine solche Tabelle umbenannt wird und in ferner Zukunft einmal gelöscht werden soll. Die Meldung 'Tabelle kann nicht gelöscht werden. Sie ist Teil einer Beziehung' ist absolut hartnäckig. Die Beziehung, auf die sich diese Meldung beruft, ist nach dem Umbenennen der Tabelle aber nicht mehr im Beziehungsfenster zu finden.
Erst mit einer typischen Arschloch-Aktion konnte dieses Problem behoben werden:
Vorausgesetzt, daß der alte Name der Tabelle bekannt ist, kann nach Wieder-Zurückbenennen der Tabelle bzw. dem Erstellen einer Dummy-Tabelle mit einem gleichlautendem Schlüssel-Feld eine der mittlerweile unsichtbaren Beziehungen wieder hergestellt werden. Wenn diese Dummy-Beziehung im Beziehungsfenster von Hand gelöscht wird, lassen sich anschließend auch die Tabellen löschen.
Unerschrockene Hardliner können sich alternativ auch an den System-Tabellen zu schaffen machen. Naja, vielleicht wird einem in einer der nächsten Versionen der Anwendungs-Quelltext nebst Compiler oder besser gleich eine Schrotflinte zur Verfügung gestellt.
Datensatz, Existenz prüfen
Quelle: dmt
Datum: 03.2004
DATENSATZ AUF EXISTENZ PRÜFEN:
Der Aufruf von
ExistsId("ID", "Leistungsrapport", Me!ID)
gibt zurück, ob ein angegebener Wert im Feld einer Tabelle gefunden werden kann.
Die Funktion erkennt auch den Null-Zustand von Zählerfeldern bei neuen Datensätzen.
Function ExistsId (Feld As String, Tabelle As String, Wert As Variant) As Integer
On Error GoTo err_ExistsId
Dim sTrennzeichen As String
If IsNull(Wert) Then Exit Function
If IsNumeric(Wert) Then
sTrennzeichen = ""
Else
sTrennzeichen = "'"
End If
If IsNull(DLookup(Feld, Tabelle, Feld & "=" & sTrennzeichen & Wert & sTrennzeichen)) Then
Exit Function
Else
ExistsId = True
End If
Exit Function
err_ExistsId:
Fehler "ExistsId"
Exit Function
End Function
Datentypen
Quelle: dmt
Datum: 03.2004
ACCESS-DATENTYPEN (Version 2.0):
Konstantenname Wert Zugehörigkeit
DB_BOOLEAN 1 Access
DB_BYTE 2 Microsoft Jet Datenbank-Engine-Datentypen
DB_INTEGER 3 Access-Basic
DB_LONG 4 Access-Basic
DB_CURRENCY 5 Access-Basic
DB_SINGLE 6 Access-Basic
DB_DOUBLE 7 Access-Basic
DB_DATE 8 Access
DB_TEXT 10 Access
LONG_BINARY 11 Microsoft Jet Datenbank-Engine-Datentypen, eigentlich DB_LONGBINARY
DB_MEMO 12 Access
Ach ja, und dann gibt es auch noch den GAR NIX - Datentyp, doch dazu mehr unter Fehler.
default
Quelle: dmt
Datum: 02.2006
STANDARDWERT / DEFAULTVALUE eines Tabellenfeldes:
Ist immer ein String und kann eigentlich sehr einfach innerhalb einer Anwendung gesetzt werden: FeldVariable("Feldname").DefaultValue = "='saukasse88'"
Klappt aber nur, wenn zu diesem Zeitpunkt kein Zugriff auf die Tabelle durch z.B. Formulare erfolgt.
STANDARDNAMEN für Felder in neuen Tabellen:
Klassisches Szenario:
In loser Form liegen Daten in zig Excel-Dateien verstreut.
Manuell werden diese Daten in eine vergleichbare Form gebracht und ab dann besser per Datenbank verarbeitet.
Super unkompliziert läuft das u.a. bereits im alten Access 2.0 (mittlerweile kann so was auch locker per phpMyAdmin in einer MySql-Datenbank abgewickelt werden, aber manchmal ist's halt schnell geklickt, vor allem in so dankbaren Anwendungen wie den älteren Accessversionen).
Locker die Importfunktion anwerfen, mit Trennzeichen getrennter Text anwählen, ein bißchen was zum Thema Text- und Feldbegrenzer sagen und schwupps sind die Daten da, wo sie hingehören (in einer Datenbanktabelle).
Die Felder sind in einfachster Form als Textfelder mit einer maximalen Länge von 255 Zeichen definiert und werden der Reihe nach numerisch benannt.
Jetzt würde man nur noch per SQL ein paar aufräumende Statements absetzen und gut ist.
Denkste !
Bereits so simple Sachen wie SELECT * FROM tabelle WHERE 1 = ""
liefern dann ganz wichtige Fehlermeldungen.
Doch warum sollte ich ausgerechnet ein Textfeld nicht gegen "" prüfen dürfen ?
Klar gibt es dafür (noch) keinen vernünftigen Grund.
Es ist vielmehr so, daß die numerischen Default-Feldnamen innerhalb von SQL-Statements Ärger machen, weil evtl. der SQL-Interpreter versucht, mathematische Sachen auszuführen. Selbst ein explizites tabelle.1 rief nur weitere Fehlermeldungen hervor.
Abhilfe:
Den Feldern flugs neue Namen gegeben (Alpha!) und die Kacke dampft nicht mehr.
Anmerkung für Leute, die nicht verstehen, was mit Alpha gemeint ist:
Gemeint sind Namen, die NUR aus Buchstaben bestehen.
Wenn Sie schon mit so komplizierten Maschinen wie Computern und noch komplizierteren Systemen wie den darauf installierten Betriebssystemen umeinander hantieren, dann tun Sie sich und anderen einen Gefallen und vermeiden Sie bitte Fehlerquellen, wo es nur geht.
Geben Sie bei Namens-Bezeichnern für Computer-technische Objekte wie Dateien, Datenbanken, Tabellen und Feldern etc. als Namen NUR Buchstaben an und vermeiden Sie ALLES, was auch nur im entferntesten kein Buchstabe ist (z.B. Leerzeichen und ähnliche Spielereien). Nennen Sie ein Feld, daß z.B. Daten einer Maß-Nummer enthält, bitte "massnr" oder meinetwegen auch "mass_nr". Spielen Sie bitte nicht mit Großbuchstaben, sprachbezogenen Sonderzeichen und anderem Quatsch herum, wenn Sie es mit einem EDV-System zu tun haben, dessen Eigen- und Unarten Sie selbst nicht überschauen können.
Ein typisches Beispiel für solche Aufräumarbeiten ist das Entfernen von leeren Zeilen, die manche Anwender in ihre Excel-Listen reinmosten, damit's übersichtlicher für's Auge wird.
Wenn die Felder gescheite Alpha-Namen haben, kann man nachschauen, welche Leerzeilen sich eingeschlichen haben:
SELECT * FROM matnr WHERE
entwicklungsnr IS NULL AND
entwicklungsnr_sap IS NULL AND
bestellnr IS NULL AND
aznr IS NULL AND
freigabenr IS NULL
Wenn es sicher ist, daß die gefundenen Zeilen wirklich Asche sind, dann machen wir ihnen mit folgendem Statement den Garaus:
DELETE * FROM matnr WHERE
entwicklungsnr IS NULL AND
entwicklungsnr_sap IS NULL AND
bestellnr IS NULL AND
aznr IS NULL AND
freigabenr IS NULL
Weitere Infos zur Schreibweise von Löschabfragen siehe auch unter LÖSCHABFRAGEN.
eingebundene
Quelle: dmt
Datum: 04.2005
EINGEBUNDENE TABELLEN
können mitunter ziemlich nützlich sein, so wie z.Bsp. im Falle der 'Runtime'-Version des HP/VHP-Managers. Hier besteht für den Anwender die Möglichkeit, zu den nicht bearbeitbaren Ventildatensätzen Extra-Memo's (Text sowie OLE) anzulegen. Die Schwierigkeit besteht darin, daß bei einem Update der Ventildatensätze die Extra-Memo's des Anwenders nicht verloren gehen sollten.
Entweder man löst dieses Problem über eine Aus- und Einlese-Software, die das während einer Update-Installation abcheckt, oder die Extra-Memo-Daten werden in einer anderen mdb-Datei abgelegt, deren Daten als 'eingebundene Tabelle' in der 'Runtime'-mdb zugänglich sind.
Diese scheinbar elegante Lösung mittels eines der tollen Features von MS-ACCESS erwies sich jedoch bereits beim ersten Problelauf bei BOSCH als Schlag ins Wasser, da auf hinterfotzige Weise der komplette Pfad der x_memo.mdb auf meinem System hinterlegt wurde.
Intensives Nachforschen ergab, daß dieser Pfad in mehreren Stellen innerhalb von MS-ACCESS hinterlegt ist:
- In der Tabelle MySysObjects
- Im Eigenschaftenfenster der eingebundenen Tabelle
Diese Einträge können allerdings nicht bearbeitet werden, da z. Bsp. die Tabelle MySys-Objects zum Eigentümer 'engine' gehört. Dies zu ändern erschien mir dann doch als zu riskant, schließlich wird EXZESS nicht mal mit seinen eigenen Geschichten (so wie sie sind) fertig.
Eine Lösung wird in der (lächerlich kleinen) Dokumentation des Developer's-Toolkit angeboten. Die mehrere Bildschirm-Seiten füllende Routine konnten für meinen Bedarf drastisch abgespeckt werden:
Sub ReAttachTables ()
Dim DB As Database, T As TableDef
Set DB = DBEngine.Workspaces(0).Databases(0)
Set T = DB("Extra_Memo")
T.Connect = ";DATABASE=X_MEMO.MDB"
T.RefreshLink
' Debug.Print T.Connect
DB.Close
End Sub
Man beachte, daß in der Connect-Anweisung der eigentliche Pfad nicht mehr enthalten ist, aber dann von Access nach Ausführen der Anweisung wieder gebildet wird.
Das geht aber auch nur dann gut, wenn diese Datei auch im aktuellen ACCESS-Verzeichnis steht. Notfalls kann dieses manuell mit 'Datei öffnen' zurechtgeklickt werden.
Diese Information wird dauerhaft in der Tabelle 'MySysObjects' hinterlegt und kann über das Eigenschaften-Fenster im Tabellen-Entwurfsmodus angezeigt werden.
Doch leider muß sich MS-ACCESS auch hier wieder Wermutstropfen-mäßig einmischen, um diesen Schritt wieder zu versauen. Beim Ausführen der RefreshLink-Anweisung zieht sich MS-ACCESS nämlich wieder den Betriebssystem-Pfad, unter dem die einzubindende Tabelle zu finden ist, rein und legt diesen dann auch brav ab.
Die oben beschriebene ReAttach-Routine muß also (am besten durch eine erweiterte Setup-Routine) auf dem Ziel-Rechner erfolgen, da ein Pfad-loses Verwalten eingebundener Tabellen ums Verrecken nicht möglich zu sein scheint.
Wenn's hierbei Probleme geben sollte, kann man es auch einmal mit TransferDatabase A_ATTACH
versuchen.
Leider gibt es auch an dieser Stelle einen Nachschlag der negativen Art:
Als die ReAttach-Routine in der 'erweiterte Setup'-Datenbank 'transact.mdb' ausgeführt wurde, funktionierte das, wie üblich, bei mir und, ebenfalls wie üblich, beim Kunden nicht.
Der Grund liegt ( verständlicherweise ) darin, daß ein ReAttachen nur in der Datenbank ausgeführt werden kann, in der die eingebundenen Tabellen auch enthalten sind. Witzigerweise erzeugt das Ausführen der Routine in einer fremden Tabelle keinen Fehler. So wird als nächstes versucht, im erweiterten Setup, indem eh' schon zwischen 2 anderen MDB's geswitcht wird, per speziellem "/X Makroname"-Parameter-Aufruf auch noch als drittes die Anwender-Datenbank direkt aufzurufen.
Auch in dieser Beziehung sind wir mittlerweile etwas weiter:
Im LaserJob-Manager führt eine laserjob.mdb, die nur Tabellen enthält, Code aus, der in lib_lj.mda enthalten ist. Eine MessageBox, die den Code in lib_lj.mda unterbricht, zeigt, daß für MS-Access die aktuelle Datenbank immer noch laserjob.mdb ist, obwohl der Code mit der Anweisung DBEngine.Workspaces(0).Databases(0) in lib_lj.mda ausgeführt wird.
Nach einem trockenem Durchschlucken erinnerte ich mich an die Funktion CodeDB(), die so freundlich ist, die Datenbank als Objekt zurückzugeben, die den gerade laufenden Code auch wirklich enthält. Mit dieser Objekt-Zuweisung ließ sich eine Lösung realisieren, in der eingebundene Tabellen innerhalb einer Bibliotheksdatenbank auf gültige Connect-Einträge hin überprüft und bei Bedarf automatisch wieder eingebunden werden, wenn sich die Herkunftsdatenbank im selben Verzeichnis befindet. Die Routine AreTableAttached stammt im Wesentlichen aus der MS-ACCESS 2.0 - Vorlagen.mdb und kann ihre Stärken immerhin bei ALLEN eingebundenen Tabellen der wahren Code-Datenbank ausspielen.
Und so gehts:
Zu Anfang werden in zwei Konstanten die Namen der bezogenen Datenbank sowie einer eingebundenen 'Test'-Tabelle vereinbart. Läßt sich eine OpenRecordset-Anweisung fehlerfrei ausführen, war der Connect-String in Ordnung. Das gilt dann auch für alle anderen eingebundenen Tabellen. Tritt ein Fehler auf, werden für alle Tabellen, die einen Connect-String enthalten, diese Strings gelöscht und die DATABASE-Zuweisung neu gesetzt. Ein RefreshLink sorgt dafür, daß Access seine benötigten Informationen selbst aktualisiert. Leider werden hierbei explizite Laufwerks- und Pfadangaben festgeschrieben. Pfadlose Informationen werden nicht behalten. Schade. Und obendrein gibt es auch noch weitere Probleme, wenn eingebundene Tabellen auf mehrere, verschiedene Datenbanken an verschiedenen Orten zeigen, aber für den Normalfall sollte ein AreTablesAttached ausreichen.
Die neueste Version schafft es sogar, Tabellen, die auf mehrere verschiedene Datenbanken verweisen, wieder automatisch einzubinden. Die einzige Voraussetzung ist, daß diese Datenbank-Dateien im selben Verzeichnis wie die Anwendung stehen.
Private Function AreTablesAttached() As Boolean
On Error GoTo err_AreTablesAttached
' **** Aktualisiert bei Bedarf den Connect eingebundener Tabellen ****
Dim TableCount As Integer, MaxTables As Integer, i As Integer
Dim FileName As String, SearchPath As String, Temp As String
Dim v As Variant, AccDir As String, DbBackend As String, DbDir As String
Dim DB As Database, RS As Recordset, TD As TableDef
Const TST_TABLE_NAME = "webmanagement"
Const NONEXISTENT_TABLE = 3011
Const NWIND_NOT_FOUND = 3024
Const ACCESS_DENIED = 3051
Const READ_ONLY_DATABASE = 3027
Set DB = CurrentDb
AreTablesAttached = True
MaxTables = DB.TableDefs.Count
On Error Resume Next ' Weiter bei ungültigen Einbindungspfaden
' **** Öffne eingebundene Tabelle; Bindungs-Informationen ok ? ****
Set RS = DB.OpenRecordset(TST_TABLE_NAME)
If Err = 0 Then
RS.Close ' Beende, wenn die eingebundenen
Exit Function ' Informationen korrekt sind.
End If
' **** Initialisiere das Wiedereinbindungsfortschritts-Meter ****
v = SysCmd(SYSCMD_INITMETER, "Tabellen werden eingebunden", MaxTables)
' **** Binde alle Tabellen ohne Nullängen-Zeichenfolgen erneut ein ****
TableCount = 1 ' Initialisiere Statuswert
DbDir = Database_Dir() ' und das Verzeichnis der aktuellen Anwendung
For i = 0 To DB.TableDefs.Count - 1
Set TD = DB.TableDefs(i)
If TD.Connect <> "" Then
FileName = DbDir & "\" & Get_Filename(TD.Connect)
TD.Connect = ";DATABASE=" & FileName
Err = 0
TD.RefreshLink
If Err <> 0 Then
If Err = NONEXISTENT_TABLE Then
MsgBox "Die Datei '" & FileName & "' enthält nicht die benötigten Tabellen '" & TD.SourceTableName & "'", 16, "Applikation nicht ausführbar"
ElseIf Err = NWIND_NOT_FOUND Then
MsgBox "Windige Nordmänner legen fragwürdiges vor.", 16, "Applikation nicht ausführbar"
ElseIf Err = ACCESS_DENIED Then
MsgBox "Die Datei '" & FileName & "' konnte nicht geöffnet werden, da sie entweder schreibgeschützt ist, oder sich auf einem schreibgeschützten Speichermedium befindet.", 16, "Applikation nicht ausführbar"
ElseIf Err = READ_ONLY_DATABASE Then
MsgBox "Die Tabellen können nicht erneut eingebunden werden, da '" & DbBackend & "' entweder schreibgeschützt ist, oder sich auf einem schreibgeschützten Speichermedium befindet.", 16, "Applikation nicht ausführbar"
Else
MsgBox Error, 16, "Applikation nicht ausführbar"
End If
AreTablesAttached = False
GoTo exit_AreTablesAttached
End If
TableCount = TableCount + 1
v = SysCmd(SYSCMD_UPDATEMETER, TableCount)
End If
Next i
exit_AreTablesAttached:
Set RS = Nothing
Set TD = Nothing
Set DB = Nothing
v = SysCmd(SYSCMD_REMOVEMETER)
Exit Function
err_AreTablesAttached:
Fehler "AreTablesAttached"
Exit Function
End Function
Im schlimmsten Fall müssen halt alle Tabellen durchlaufen werden, bei enthaltenen Connect-Strings wird die Zugänglichkeit einzeln überprüft (allein das kann dauern) und bei Mißlingen sollte dem Anwender in einem Dialog (evtl. auch noch mit DateiÖffnen) Pfad und Name der letzten Connect-Anweisung genannt werden.
Es besteht aber auch die Möglichkeit, den Access-Dialog zum manuellen Wiederherstellen von Einbindungs-Informationen in eine Anwendung einzubauen s. bestatt.mdb. Da muß meines Wissens aber noch eine soa200.dll oder so mitgeliefert werden.
Eine manuelle Lösung, die mit wenig Aufwand dem Benutzer per InputBox-Eingabe die Möglichkeit gibt, einen fehlerhaften Connect-String zu korrigieren, kann so aussehen (auch die Übergabe eines Tabellennamens per Parameter ist denkbar):
Private Function Check_LogDir() As Boolean
On Error GoTo err_Check_LogDir
Dim DB As Database, RS As Recordset, TD As TableDef, s As String
Const WEBLOG_TABLE_NAME = "logfiles"
Set DB = CurrentDb
On Error Resume Next ' Weiter bei ungültigen Einbindungspfaden
' **** Öffne eingebundene Tabelle; Bindungs-Informationen ok ? ****
Set RS = DB.OpenRecordset(WEBLOG_TABLE_NAME)
If Err = 0 Then
RS.Close ' Beende, wenn die eingebundenen
Check_LogDir = True
Exit Function ' Informationen korrekt sind.
End If
Set TD = DB.TableDefs(WEBLOG_TABLE_NAME)
Beep
s = "Der Verbindung-String der Tabelle " & WEBLOG_TABLE_NAME & ": " & TD.Connect & " ist ungültig !"
s = s & Chr$(13) & Chr$(10) & Chr$(13) & Chr$(10)
s = s & "Geben Sie einen gültigen Verbindungs-String ein."
s = s & Chr$(13) & Chr$(10) & Chr$(13) & Chr$(10)
s = s & "Abbrechen umgeht diese Prüfung."
s = InputBox(s, "Check_LogDir", TD.Connect)
If s = "" Then
Exit Function
Else
TD.Connect = s
Err = 0
TD.RefreshLink
If Err <> 0 Then
Call Check_LogDir
Else
Beep
Check_LogDir = True
MsgBox "Die Weblogs-Tabelle wurde erfolgreich eingebunden !", vbInformation, "Check_LogDir"
End If
End If
Exit Function
err_Check_LogDir:
Fehler "Check_LogDir"
Exit Function
End Function
* * * *
Externer Access-Tip: Pfade von verknüpften Tabellen
In Datenbanken mit vielen eingebundenen Tabellen, ist es nicht immer möglich zu erkennen, auf welche Datenbank die jeweilige Tabellen zurückgreift. Die nachfolgende Funktion liest die Pfade aller eingebundenen Tabellen einer Datenbank aus und zeigt diese im Direktfenster an.
Public Function LinkPfadAuslesen()
' ---------------------------------------------------------------
' mit dieser Funktion werden alle eingebundenen
' Tabellen gelöscht
' ---------------------------------------------------------------
On Error GoTo fehler
Dim DB As Database
Dim rs As Recordset
Dim td As TableDef
Set DB = CurrentDb()
Set rs = DB.OpenRecordset("SELECT Name FROM MSysObjects " & _
"WHERE Name Not Like 'MSys*' AND Type=6;", dbOpenSnapshot)
rs.MoveFirst
Do Until rs.EOF
Set td = DB.TableDefs(rs!Name)
Debug.Print rs!Name & " - " & Mid(td.Connect, 11)
rs.MoveNext
Loop
rs.Close
DB.Close
ende:
Exit Function
fehler:
Resume ende
End Function
Anmerkung:
In den Verweisen muss die Bibliothek "Microsoft DAO 3.6 Object Libary" aktiviert sein.
Um in Access einen Verweis auf eine andere Bibliothek einzustellen gehen Sie bitte folgendermaßen vor:
1. öffnen Sie in Ihrer Access-Datenbank ein beliebiges Modul
2. über das Menü [Extras][Verweise] können Sie den Dialog zum Einstellen der Verweise öffnen
3. suchen Sie in der Liste den Verweis auf die Bibliothek "Microsoft DAO 3.6 Object Libary" und aktivieren diesen
4. schieben Sie den Verweis an die oberste mögliche Stelle in der Liste (mit den Schaltflächen 'Priorität')
5. schließen Sie den Dialog
* * * *
Mini-Routine zum Auslesen eines AttachedTable-Verweises:
Function GetConnectDb(sTable As String) As String
On Error GoTo err_GetConnectDb
Dim DB As Database, TD As TableDef
Set DB = CurrentDb
Set TD = DB.TableDefs(sTable)
GetConnectDb = ReplaceInString(CStr(TD.Connect), ";DATABASE=", "")
Set TD = Nothing
Set DB = Nothing
Exit Function
err_GetConnectDb:
Fehler "GetConnectDb für Tabelle " & sTable
Exit Function
End Function
Gültigkeitsregeln
Quelle: dmt
Datum: 02.2006
GÜLTIGKEITSREGEL / VALIDATION RULE:
Eigentlich eine feine Sache, bereits bei der Tabellen-Feld-Definition oder im Formular ungültige Werte für ein Feld innerhalb der Eigenschaften abzufangen und sogar eine aussagekräftige Meldung zu präsentieren. In einer Formularlösung ergab sich jedoch, daß, wenn mittels einer ungültigen Eingabe die Verletzungsmeldung kam, selbst die Korrektur des Wertes nach Bestätigung wieder die Meldung hervorbrachte. Da ich keine Lust hatte, zu testen, ob dieser Verhaltensfehler auch bei Implementierung in den Tabellenfeld-Eigenschaften auftritt, habe ich es wie so oft vorgezogen, das Problem per Code zu lösen.
In den späteren 32-Fick-Access-Varianten kann dann mit <Esc>
-Spielereien eine formulargebundene Gültigkeitsregel umgangen werden. Bravo, ihr Schwachköpfe !
Angeblich sind ValidationsRules in Tabellen besser aufgehoben.
relational
Quelle: dmt
Datum: 02.2006
ANLEGEN RELATIONAL VERKNÜPFTER DATEN:
Gesetzt den Fall, zwei Tabellen ( z.Bsp. Lieferanten und Adressen ) sind über ACCESS-Beziehungen miteinander referentiell 1:1 verknüpft. Der Anwender arbeitet mit einem Formular, dessen Abfrage ebenfalls eine SQL-Verknüpfung der beiden Tabellen enthält.
Wird nun ein Datensatz angelegt, wird, wenn der Anwender mit dem Lieferanten-Formular arbeitet, aber nur ein Lieferanten-Datensatz angelegt. Vom Adressendatensatz trotz referentieller Integrität weit und breit keine Spur. Wird aber im Lieferantenformular für ein Feld, das in beiden Tabellen vorkommt ( z.Bsp. Suchname ) die Herkunftseigenschaft der anderen ( hier Adresse ) Tabelle eingestellt, wird
1. Im Lieferanten.Suchname der Name übernommen und
2. ein neuer Adressen-Datensatz mit dem angegebenem Suchnamen angelegt.
Allerdings ist damit noch nicht alles erledigt.
Bis man einen Anwender auf diese Formulare loslassen kann, muß doch noch einiges eingestellt werden ( siehe Formulare KV_Lizenznehmer und KV_Adressen in hp_vhp.mdb ). So mußte z. Bsp. die Eigenschaft DefaultEditing auf 'Bearbeiten' eingestellt werden, damit beim Ändern eines Datensatzes das Formular automatisch geschlossen werden kann. Somit war es aber möglich, in einen neuen Unter-Adress-Datensatz zu gehen ( unerlaubt ), wenn der aktive nicht geändert wurde. Jegliches Umstellen der DefaultEditing-Eigenschaft zur Laufzeit stellte alle Datensätze anstatt des einen per Bedingung übergebenen dar. So wurde versucht, bei Form_Current die IstNull-Eigenschaft des Feldes 'Suchname' zu überprüfen, um dann das Formular zu schließen. Hierbei tritt ein Laufzeitfehler auf. Führt man statt 'DoCmd Close' ein 'SendKeys "^{F4}" aus, klappt die Sache (besser DoCmd Close A_FORM, Me.Name).
Nach einer Nacht Bedenkzeit habe ich mich dazu entschlossen, die relationale Konstruktion fallen zu lassen, und durch eine 1-Tabellen-Lösung zu ersetzen. Die getrennte Darstellung der Daten mit zwei verschiedenen Formularen habe ich beibehalten. Das hatte allerdings leider zum Ergebnis, daß ich die Ereignis-Kacke mit dem Thema automatisches Hin- und Herblenden immer noch hatte.
Auch das Darstellen relationaler Daten in Formularen will erkämpft sein.
Eine Basistabelle soll durch ein Formular bearbeitet werden können, indem zu dem jeweils aktiven Datensatz dazugehörende Daten in anderen Tabellen angezeigt werden sollen. Normalerweise kann das mit Unterformularen gut gelöst werden, die aber in der Listen- bzw. Endlosformular-Darstellung des Hauptformulares nicht angezeigt werden können ( die Endlosformular-Darstellung verbietet sich sogar, wenn das Hauptformular Unterformulare enthält ). Eine hart erkämpfte Lösung besteht darin, einem Kombinationsfeld, das an ein Feld der Basistabelle gebunden sein muß, über das Ereignis 'FormCurrent' des Formulares einen SQL-String als RowSource zuzuweisen, der eine relationale Verknüpfung zu den Daten einer anderen Tabelle darstellt. Dieses Kombinationsfeld erfüllt seinen Zweck in ALLEN drei Formularansichten und gibt die enthaltenen Daten Listen-mäßig nur dann preis, wenn dessen Schaltfläche betätigt wird. Somit wird der entsprechende Datensatz automatisch zum aktiven und die Kombinationsfeld-Liste zeigt die gewünschten Daten. Über die Ereignisse 'DblClick' und 'Key_Down' kann auch ein anderes Formular mit einer Bedingung geöffnet werden, die den Datensatz des Hauptformulares mit den Daten der anderen Tabelle darstellt.
SPLITTING von Rahmen- und Detail-Daten:
Bei der klassischen Trennung von z.B. Auftragsdaten und Auftragspositionen konnte es trotz referentieller Beziehung passieren, daß Unterdaten angelegt wurden, obwohl kein Master-Datensatz besteht. Wurde der Vorgang abgebrochen, blieben die Unterdaten als Geister-Datensätze übrig. Man kann aber in BeforeInsert im Unterformular-Formular prüfen, ob ein Masterdatensatz existiert (z.B. mit Isnull(ZählerID)) und mittels Cancel=true und einer Meldung schlimmstes verhindern.
Hier sollte aber auch die Art der Verknüpfung (1:n, Left Jion etc. eine Rolle spielen).
Struktur auswerten
Quelle: dmt
Datum: 10.2004
Feststellen, ob ein Objekt ein SYSTEMOBJEKT ist:
Beim Durchnudeln z. B. aller in einer Datenbank enthaltenen Tabellen kann es vonnöten sein, daß die ebenfalls enthaltenen Systemtabellen ausgeblendet werden.
Hierfür wird in verschiedenen Dokumentationen der obskure Prüfvergleich
If (TD.Attributes And DB_SYSTEMOBJECT) then
angeboten. Da aber in TableDef.Attributes auch Flags enthalten sind, die auch andere als nur Systemtabellen-Zeiger enthalten, kann davon ausgegangen werden, daß bei diesem Vergleich evtl. auch Nicht-Systemobjekte unterschlagen werden. Das könnten z.B. eingebundene oder andere anormale Tabellen sein, bei den ein entsprechendes DB_ARSCHFLAG gesetzt ist und der oben beschriebene Vergleich evtl. dann negativ ausfällt. So ganz trau' ich der Sache nicht, aber erste Erfolge können bewundert werden in ANALYZER.MDB im Formular 'Analyze_Tabellen_Update2'.
Mehr dazu siehe unten ...
Ebenfalls fündig wird man in den Routinen des Formulares 'Check_Tables', das enthaltene Tabellen, deren Eigenschaften, darin enthaltene Felder sowie deren Eigenschaften auflistet.
LIST_TABLES liefert einen Listen-/Klappfeld-kompatiblen Rowsource-String und gibt je nach Parameter die gewünschte Art von Tabellen zurück:
Function List_Tables (Modus As String) As String
On Error GoTo err_List_Tables
' **** Füllt die Werte-Liste des übergebenen Kombifeldes ****
' **** mit den Namen der Tabellen der angegebenen Art ****
Dim DB As Database, i As Integer, s As String, v As Variant
v = SysCmd(SYSCMD_SETSTATUS, "lese Tabellennamen ein ...")
Set DB = DBEngine.Workspaces(0).Databases(0)
Select Case Modus
Case "Eingebundene": For i = 0 To DB.TableDefs.Count - 1
If (DB.TableDefs(i).Attributes And DB_ATTACHEDTABLE) Then
s = s & DB.TableDefs(i).Name & ";"
End If
Next i
Case "Alle": For i = 0 To DB.TableDefs.Count - 1
s = s & DB.TableDefs(i).Name & ";"
Next i
Case "System": For i = 0 To DB.TableDefs.Count - 1
If (DB.TableDefs(i).Attributes And DB_SYSTEMOBJECT) Then
s = s & DB.TableDefs(i).Name & ";"
End If
Next i
Case "Eingebundene und System": For i = 0 To DB.TableDefs.Count - 1
If (DB.TableDefs(i).Attributes) Then
s = s & DB.TableDefs(i).Name & ";"
End If
Next i
Case "Normale": For i = 0 To DB.TableDefs.Count - 1
If (DB.TableDefs(i).Attributes) = 0 Then
s = s & DB.TableDefs(i).Name & ";"
End If
Next i
Case "Keine System": For i = 0 To DB.TableDefs.Count - 1
If (DB.TableDefs(i).Attributes And DB_SYSTEMOBJECT) = 0 Then
s = s & DB.TableDefs(i).Name & ";"
End If
Next i
Case Else: Beep
MsgBox "Unbekannter Modus '" & Modus & "' !", 16, "List_Tables"
v = SysCmd(SYSCMD_CLEARSTATUS)
Exit Function
End Select
s = Left$(s, Len(s) - 1)
List_Tables = s
v = SysCmd(SYSCMD_CLEARSTATUS)
Exit Function
err_List_Tables:
v = SysCmd(SYSCMD_CLEARSTATUS)
Fehler "List_Tables"
Exit Function
End Function
Um Tabellen zu 'fangen', die nicht eingebundene oder Systemtabelle sind, muß
If (DB.TableDefs(i).Attributes)
in
If (DB.TableDefs(i).Attributes)=false
umformuliert werden. Ein If Not (... geht nicht !
Und jetzt das ganze mit LIST_TABLEPROPERTIES für die 'internen' Eigenschaften der Tabellen. Obendrein gibt es sog. 'benutzerdefinierte' Eigenschaften wie 'DataSheetHeight' o.s.ä. und echte benutzerdefinierte Eigenschaften (z.B. Tabelle.beschissen=true). Laut Doku müssen diese explizit benannt werden. In der Praxis war da bisher noch nichts zu holen.
Function List_TableProperties (TN As Variant) As String
On Error GoTo err_List_TableProperties
' **** Füllt die Werte-Liste des übergebenen Kombifeldes ****
' **** mit den Eigenschaften der übergebenen Tabelle ****
Dim DB As Database, TD As TableDef, i As Integer, s As String
If IsNull(TN) Then
List_TableProperties = ""
Exit Function
End If
Set DB = DBEngine.Workspaces(0).Databases(0)
Set TD = DB.TableDefs(TN)
' 'eingebaute' Eigenschaften
For i = 0 To TD.Properties.Count - 1
s = s & TD.Properties(i).Name & ";'" & TD.Properties(i) & "';"
Next i
s = Left$(s, Len(s) - 1)
List_TableProperties = s
Exit Function
err_List_TableProperties:
Fehler "List_TableProperties"
Exit Function
End Function
LIST_FIELDS macht das, was der Name schon sagt:
Function List_Fields (TN As Variant) As String
On Error GoTo err_List_Fields
' **** Füllt die Werte-Liste des übergebenen Kombifeldes ****
' **** mit den Feldnamen der übergebenen Tabelle ****
Dim DB As Database, TD As TableDef, i As Integer, s As String
If IsNull(TN) Then
List_Fields = ""
Exit Function
End If
Set DB = DBEngine.Workspaces(0).Databases(0)
Set TD = DB.TableDefs(TN)
For i = 0 To TD.Fields.Count - 1
s = s & TD.Fields(i).Name & ";"
Next i
s = Left$(s, Len(s) - 1)
List_Fields = s
Exit Function
err_List_Fields:
Fehler "List_Fields"
Exit Function
End Function
LIST_INDEXES ist auch noch nicht das Ende der Fahnenstange:
Function List_Indexes (TN As Variant) As String
On Error GoTo err_List_Indexes
' **** Füllt die Werte-Liste des übergebenen Kombifeldes ****
' **** mit den Namen der Indizes der übergebenen Tabelle ****
Dim DB As Database, TD As TableDef, i As Integer, s As String
If IsNull(TN) Then
List_Indexes = ""
Exit Function
End If
Set DB = DBEngine.Workspaces(0).Databases(0)
Set TD = DB.TableDefs(TN)
For i = 0 To TD.Indexes.Count - 1
s = s & TD.Indexes(i).Name & ";"
Next i
If Len(s) > 0 Then
s = Left$(s, Len(s) - 1)
End If
List_Indexes = s
Exit Function
err_List_Indexes:
Fehler "List_Indexes"
Exit Function
End Function
LIST_FIELDPROPERTIES; auch hier das Spiel mit den eingebauten Eigenschaften:
Private Function List_FieldProperties (TN As Variant, FN As Variant) As String
On Error GoTo err_List_FieldProperties
' **** Füllt die Werte-Liste des übergebenen Kombifeldes ****
' **** mit den Eigenschaften der überg. Tabelle und Feld ****
Dim DB As Database, TD As TableDef, FD As Field, i As Integer, s As String, s1 As String
If IsNull(TN) Or IsNull(FN) Then
List_FieldProperties = ""
Exit Function
End If
Set DB = DBEngine.Workspaces(0).Databases(0)
Set TD = DB.TableDefs(TN)
Set FD = TD.Fields(FN)
' 'eingebaute' Eigenschaften
For i = 0 To FD.Properties.Count - 1
Select Case FD.Properties(i).Name
Case "Attributes": Select Case FD.Properties(i)
Case DB_FIXEDFIELD: s1 = "fixe Größe, def. bei num."
Case DB_VARIABLEFIELD: s1 = "variable Größe, Textfelder"
Case DB_AUTOINCRFIELD: s1 = "Zähler, Long"
Case DB_UPDATABLEFIELD: s1 = "Wert veränderbar"
Case DB_DESCENDING: s1 = "Feld wird in absteigender Reihenfolge sortiert"
End Select
Case "Type": Select Case FD.Properties(i)
Case DB_DATE: s1 = "Datum/Uhrzeit"
Case DB_TEXT: s1 = "Text"
Case DB_MEMO: s1 = "Memo"
Case DB_UPDATABLEFIELD: s1 = "Wert veränderbar"
Case DB_BOOLEAN: s1 = "Ja/Nein"
Case DB_INTEGER: s1 = "Integer"
Case DB_LONG: s1 = "Long"
Case DB_CURRENCY: s1 = "Currency"
Case DB_SINGLE: s1 = "Single"
Case DB_DOUBLE: s1 = "Double"
End Select
Case Else: s1 = ""
End Select
s = s & FD.Properties(i).Name & ";'" & FD.Properties(i) & "';" & s1 & ";"
Next i
s = Left$(s, Len(s) - 1)
List_FieldProperties = s
Exit Function
err_List_FieldProperties:
If Err = 3219 Then
Resume Next
Else
Fehler "List_FieldProperties"
Exit Function
End If
End Function
