#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):

Statt der Daten wird #Gelöscht angezeigt

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:

Ein neu angelegter Datensatz wird als gelöscht angezeigt

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.2016
    Könnte es sein, dass in der entsprechenden per ODBC-verknüpften SQL Server-Tabelle keine TimeStamp-Spalte eingefügt ist. Access benötigt die Timestamp-Spalte, um Änderungen z.B. an Memo-Feldern nachzuvollziehen. Ich hatte im Mehrbenutzerbetrieb durch fehlende Memo-Felder auch schon Schreibkonflikte, die sich durch das Einfügen selbiger "verflüchtigt" haben. Hier ein weiterführender Link: http://stackoverflow.com/questions/3248967/write-conflict-messages-suddenly-start-happening-in-odbc-linked-tables/3302588#3302588

    Viele Grüße, Mathias Wieland
    • Anna
      am 04.11.2016
      Hallo Mathias,
      danke 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.2017
    Hallo,

    ich 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.2018
    Bei mir hat es mit F5 geklappt :-)
  • Nick Oetjen
    am 23.10.2018
    Hallo,

    danke 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.2019
    Hallo Zusammen

    Ich 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.2019
      Hallo Isidor,

      wir 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.2019
    Hallo,

    Dieses "#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"
  • Walter
    am 14.11.2019
    Ich hatte auch das "#Gelöscht"-Problem. Und die Ursache war ein Datentyp den Access nicht "verdauen" kann. Und zwar Big-Integer als Primärfelddatentyp.
    Diesen 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.2019
      Danke für die Ergänzung!
  • UDO
    am 16.12.2019
    Ich hatte dieses Problem auch. Die Loesung war, bei allen Textfelder die Kollation utf8_general_ci zu belassen (oder zu aendern) und als ODBC Treiber nicht den neusten zu nehmen(mysql-connector-odbc-5.3.14-win32.msi), sondern mysql-connector-odbc-5.1.8-win32.msi (oder gegebenenfalls mysql-connector-odbc-5.1.8-win64.msi. Funktioniert jetzt bei mir mit MSACCESS 2019, PLESK und php 7.2.25
    • Joachim
      am 17.12.2019
      Vielen Dank für die Ergänzung, ich verwende bei einigen Projekten mit mySQL-Datenbanken ebenfalls den älteren Treiber. Weil der funktioniert, habe ich ihn bisher nicht aktualisiert und werde es dann wohl mit Ihrer Informationen im Hinterkopf auch nicht tun.
  • Thomas
    am 17.02.2023
    Ich habe bei den Abfrageeigenschaften in Access das Merkmal Recordset von Dynset nach Snapshot geändert, schon hats funktioniert.
    • Anna
      am 19.02.2023
      Das kann man aber nur dann machen, wenn man die Daten in der Abfrage nicht ändern möchte.

Neue Antwort auf Kommentar schreiben