Der Vorteil der Klugheit besteht darin, dass man sich dumm stellen kann. Das Gegenteil ist schon schwieriger. - Kurt Tucholsky
emde IT-LÖSUNGEN
> Tel. 08131 / 99 69 80-0
> Kontakt per E-Mail
#Gelöscht in Datenfeldern
28. Dezember 2015
Als Datenbankentwickler mit Access ist man immer wieder mit der Situation konfrontiert, dass Access in den Datenzeilen statt der erwarteten Tabelleninhalte #Gelöscht anzeigt oder dass nach dem Speichern von neuen Datensätzen falsche Daten angezeigt werden. Das sieht dann zum Beispiel so aus (Bild zeigt die englische Variante):
In den meisten Fällen, in denen Access statt der gewünschten Daten ein #Gelöscht anzeigt, gibt es eine einfache Ursache: Access speichert das Ergebnis einer Datenbankabfrage meist in sogenannten Dynasets. Das bedeutet, dass nicht die kompletten Datenbankinhalte des entsprechenden Recordsets sondern nur die entsprechenden PrimaryKeys gespeichert werden, was natürlich viel weniger Speicherplatz und Zeit bei der Datengenerierung benötigt. Erst wenn man sich die entsprechende Daten zum Beispiel durch Herunterscrollen wirklich anschauen möchte, werden die zu einem PrimaryKey gehörenden Daten aus der Datenbank ausgelesen.
Das funktioniert jedoch nur dann gut, wenn für alle Tabellen "vernünftige" PrimaryKeys definiert sind. Vernünftig bedeutet in dem Zusammenhang, dass für jede Tabelle eine Autowert-Spalte als Zähler mit der PrimaryKey-Eigenschaft eingerichtet wird. Alle anderen PrimaryKeys wie zum Beispiel Fließkommazahlen oder Strings sind eigentlich nicht geeignet. Weitere eindeutige Schlüssel, sogenannte CandidateKeys, können unabhängig davon noch zusätzlich definiert werden. Der für die Autowert-Spalte benötigte zusätzliche Speicherplatz spielt auf heutigen Computersystemen denke ich keine Rolle mehr.
Mit der oben beschriebenen Regel sind die allermeisten Probleme in Bezug auf die #Gelöscht-Datensätze behoben. Es gibt jedoch insbesondere im Zusammenspiel mit SQL-Servern ganz hartnäckige Fälle, in denen nach dem Speichern eines Datensatzes entweder #Gelöscht oder auch ein komplett anderer Datensatz angezeigt wird. Dies zeigt der untenstehende Screenshot:
Ich habe schon Stunden erfolglos damit zugebracht, eine Systematik und daraus resultierend eine Umgehung für dieses Problem zu finden. Am Ende habe ich akzeptiert, dass beim Anlegen eines neuen Datensatzes die korrekte ID des neuen Datensatzes aus irgendeinem Grund nicht den Weg zurück zu Access-Applikation findet, sondern Access von einem falschen Wert ausgeht. Da diese Beobachtung eigentlich nur bei Tabellen mit verhältnismäßig vielen Datensätzen gemacht wurde, vermute ich irgendeine Form von Überlauf in der Datenbankschnittstelle.
Ich habe in meiner Not dann etwas eigentlich Unschönes gemacht, das in meinen Applikationen das Problem aber zum Verschwinden gebracht hat: Vor Aktualisierung des Datensatzes bestimme ich von der Applikation aus den nächsten freien Autowert. Dies ist eigentlich die primäre Aufgabe der Datenbank, führt aber, wie oben beschreiben, zu einem Fehler. Da es sein kann, dass genau in demselben Augenblick ein anderer Client ebenfalls den nächsten freien Autowert bestimmen will, kopple ich diesen Vorgang noch mit einer Sperrfunktion, so dass immer nur ein Client zur Zeit einen neuen Autowert bestimmen kann. Ist die Funktion gesperrt, warte ich einen Moment und probiere es dann noch einmal. Da die Anzahl der Benutzer in den Applikationen, in denen dieser Workaround zum Einsatz kommt, nicht zu groß ist, hat es hier noch nie Probleme gegeben. Das ist zwar keine wirklich schöne Programmierung, aber manchmal heiligt der Zweck halt die Mittel.
Jetzt aber noch einmal im Detail: Im Event-Handler des Ereignisses Vor Aktualisiertung weise ich dem Feld mit der ID den selbst ermittelten ID-Wert zu. Etwa so:
FTei!AufTei_ID = AutoWert("Auftraege_Teile", "AufTei_ID")
Die Funktion Autowert selbst sieht so aus:
Function AutoWert(Tabelle As String, Autowertspalte As String, Optional Sperr As Boolean = True) As Long ' ' Nach dem Speichern eines Datensatzes wird dieser noch einmal neu geladen. Manchmal passieren dabei Fehler, ' was sich in #gelöscht-Einträgen oder einfach in einem falsch angezeigten Datensatz mainfestiert. Darum wird der ' Wert des Zählerfeldes vor dem Speichern bestimmt. ' Dim SName As String ' wird für die Funktion Sperren benötigt Dim MaxID As Variant Dim I As Integer GlobVarSetzen ' Sperrung für das Erzeugen eines Autowertes erzeugen If Sperr Then For I = 1 To 10 If (Sperren("Autowert " & Tabelle, SName)) Then Exit For End If ' 2 Sekunden warten sSleep 2000 If I = 10 Then Err.Raise 12345, "AutoWert()", "Es kann kein neuer Autowert für Tabelle " & Tabelle & " erzeugt werden." Exit Function End If Next I End If MaxID = DMax(Autowertspalte, Tabelle) If IsNull(MaxID) Then AutoWert = 1 Else AutoWert = MaxID + 1 End If EntSperren "Autowert " & Tabelle End Function
Die Funktion Sperren() setzt dabei eine Semaphore für den Vorgang "Autowert in Tabelle X setzen" und verhindert so, dass zwei Benutzer gleichzeitig versuchen, einen Autowert für dieselbe Tabelle zu erzeugen. Wir haben dazu in der Datenbank eine Tabelle "Sperrungen". Beim Aufruf von Sperren() wird in dieser Tabelle ein Eintrag für den Autowert-Vorgang generiert - sofern es nicht schon einen Eintrag zu diesem Vorgang gibt, weil nämlich ein anderer Benutzer gerade einen Autowert für die Tabelle erzeugt; in diesem Fall bekommt der Benutzer eine Rückmeldung, dass er es gleich noch einmal versuchen soll. Die Funktion Entsperren() hebt nach Durchführung des Vorgangs die Sperrung wieder auf.
Die Funktion Sperren() kann natürlich nicht nur für die Erstellung von Autowerten, sondern für alle Vorgänge verwendet werden, die immer nur ein Benutzer zur Zeit ausführen darf.
Für die Umsetzung gibt es diverse Möglichkeiten. Der untenstehende Code ist deshalb nur als Beispiel zu verstehen.
Function Sperren(Art As String, SName As String) As Boolean ' Setzt Sperrung für aktuellen User Dim SQL As String Dim R As New ADODB.Recordset SQL = "SELECT * FROM Sperrungen WHERE (Art = '" & Art & "')" R.Open SQL, SQLCon, adOpenKeyset, adLockOptimistic If R.EOF Then ' Sperrung kann gesetzt werden With R .AddNew !Art = Art !Benutzer = Akt_Login !Datum = Now() .Update End With Sperren = True ElseIf (R!Benutzer = Akt_Login) Then ' Sperrung vom selben Benutzer -> darf überschrieben werden With R !Benutzer = Akt_Login !Datum = Now() .Update End With Sperren = True ElseIf DateDiff("d", R!Datum, Date) > 1 Then ' Sperrung ist älter als 1 Tag und darf überschrieben werden With R !Benutzer = Akt_Login !Datum = Now() .Update End With Sperren = True Else ' Sperrung nicht möglich; SName für qualifizierte Rückmeldung an Benutzer SName = R!Benutzer Sperren = False End If CloseObj R Exit Function End Function
Kommentare (9)
Mathias Wieland
am 04.11.2016Viele Grüße, Mathias Wieland
Anna
am 04.11.2016danke für den Kommentar. In unseren SQL-Datenbanken haben alle Tabellen grundsätzlich ein Timestamp-Feld - das kann also hier nicht das Problem sein. Es ist aber auf jeden Fall gut, noch mal darauf hinzuweisen, dass ein Timestamp unerlässlich ist.
Schöne Grüße, Anna
Gerhard
am 26.01.2017ich habe diesen Fehler auch in der Konstellation MS Access/SQL Server gehabt und Stunden mit Recherche und Fehlersuche verbracht.
Ursache war in der Tat ein falsch gesetzter Primärschlüssel. Nun funktioniert es! :-)
Vielen Dank für den 'Denkanstoß'
Grüße Gerhard
Can
am 09.10.2018Nick Oetjen
am 23.10.2018danke für die Gedankenanstösse. Leider keine Lösung dabei: Ich hatte eine Verknüpfung auf drei Tabellen, die nur bei einer Kollegin nicht funktionierte, bei allen anderen schon.
Nach der Neusetzung funktionieren nun bei allen zwei von drei Tabellen. Von diesen hat eine einen TimeStamp, die andere nicht. Alle drei haben saubere Primärschlüssel bestehend aus einem einzelnen Zahl-Attribut. Keine der Tabellen wurde geändert, nur die ODBC-Verbindung ist neu.
Dieses Setup scheint einfach nicht stabil genug.
Viele Grüße
Nick
Isidor
am 01.06.2019Ich habe/hatte dieses Problem mit #Gelöscht auch.
Trotz TimeStamp und Autowert wurden in Sub-Formularen nach dem Speichern bei einzelne Datensätzen in allen Feldern #Gelöscht angezeigt. Nach Aktualisierung mit F5 wurden die Datensatz dann richtig angezeigt.
Das Problem war, dass ich NULL Werte in einzelne Felder (Text- oder Zahlenfelder) geschrieben habe. Seit ich explizit mit der Funktion NZ() 0 in Zahlenfelder oder Leerstring "" in Textfelder schreibe ist der Fehler behoben.
Dies ist zwar eine vertretbare Lösung, sollte aber jemand eine Idee haben wie trotzdem NULL Werte in ein Feld geschrieben werden können währe ich sehr dankbar, da ja Leerstring nicht gleich NULL ist!
Grüsse Isidor
Anna
am 25.06.2019wir haben uns Ihre Datenbank nun mal angesehen.
Das Problem ist, dass Access nicht mitbekommt, dass es sich bei der ID in der Subtabelle um einen Autowert handelt. Das sieht man daran, dass in Access im Entwurfsmodus der Tabelle hier nur "Zahl" steht. Legt man nun einen neuen Datensatz an, so bekommt Access nicht mit, dass die ID gesetzt wurde; es kann also anhand des Primary key den Datensatz nicht noch einmal laden (was es ja tut, um zu prüfen, ob jemand anders den Datensatz inzwischen geändert hat) und meldet "#Gelöscht". Wenn man das Feld "ID" in das Unterformular integriert und hier vor dem Speichern einen Wert einträgt, ist alles ok.
Entweder bekommen Sie Access dazu, die Eigenschaft "Autowert" richtig zu erkennen, oder Sie müssen möglicherweise den Wert vor dem Speichern jedes Mal per Funktion ermitteln (Max+1) und eintragen.
Anna
Bob Houston
am 10.10.2019Dieses "#Gelöscht" in Verbindung Access mit SQL Datenbank kommt bei mir wenn ich bei 1:n Datenbanken im Primärschlüsselfeld zb. folgendes Sonderzeichen im Text verwende: "→" (U+2192 Pfeil nach rechts, in Zeichentabelle Windows Arial).
Schlimm war es, als ich im Feld nachträglich nachträglich diesen "→" eingegeben habe, denn dann waren alle Datensätze in der darunterliegenden Tabelle ebenfalls "#Gelöscht"
Anna
am 10.10.2019Walter
am 14.11.2019Diesen habe ich jetzt auf INT gesetzt und nach aktualisierung in Access funktioniert alles wieder.
Vielen Dank für diesen Artikel!
Beste Grüße
Anna
am 14.11.2019UDO
am 16.12.2019Joachim
am 17.12.2019Thomas
am 17.02.2023Anna
am 19.02.2023