Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten (https://www.delphipraxis.net/210489-trystrtofloat-trystrtoint-leeren-string-nicht-als-0-erhalten.html)

cltom 30. Apr 2022 13:40

Delphi-Version: XE2

TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Hallo,

ein Thema, das bestimmt zigfach gelöst ist, ich hab aber keine gute Lösung dazu, die nicht einen irren Aufwand verursacht.

Es sollen Werte aus Edit-Feldern in diverse Variablen geschrieben werden. Dafür wird TryStrToInt/TryStrToFloat verwendet. Die Werte werden numerisch gebraucht für einige Rechnungen. Dann wird alles in eine SQL-DB geschrieben. Später geht der Weg natürlich wieder retour, aus der SQLite ins Objekt, von da wieder ins UI via FloatToStr(...).

Nun kann es sein, dass manche Eingabefelder leer bleiben, der User also einfach nichts eingibt. Das soll auch erlaubt sein. Allerdings: es soll nun beim Lesen auch wieder nichts drinstehen, nicht "0". Wie kriegt man das hin, dass entlang obiger Kette das "nichts" erhalten bleibt und nicht eine 0 rauskommt.


Die Themen fangen schon damit an, dass beim TryStrToFloat bei einem ungültigen String als Eingabe ja letztlich ein Wert in der Variable vorhanden ist. Beim TryStrToInt kommt Null raus.


Die Varianten, die mir mal eingefallen sind:

1. das Ergebnis von TryStrToInt/TryStrToFloat verwenden
TryStrToInt/Float hab ich ja schon, da kann ich ja das result nehmen. Aber wie weiter? Ich müsste ja dann für jede Variable einen bool mitführen, der das Ergebnis von TryStrToInt enthält, um später zu entscheiden, ob der Wert in die DB geschrieben wird. Denn sobald Null drinsteht, landet Null in der DB und ich kann später nicht mehr sagen, ob die Null eine Eingabe war oder beim TryStrToInt entstanden ist. Die SQL-Anweisungen werden dann allerdings ein absurder Aufwand, weil ich für jede Variable einzeln prüfen muss, ob ihr zugehöriger "bin-eine-echte-Null"-bool wahr ist. Allein die Prüfung TryStrToInt verhindert ja nicht, dass in der Variable eine Null landet, wenn der string='' ist
Variation: den bool führen und zusätzlich mit in die DB aufnehmen. Dann bleibt der SQl-Zugriff unberührt ... enorm aufgebläht um zahhlose bools zwar ... schick ist anders.

2. bei einem false von TryStrToX einen "Markerwert" in die Variable setzen
Also beim false von TryStrToX der Variable einen Wert verpassen, der exotisch genug ist, dass er real sonst nicht vorkommt. Damit kann man alles machen, die DB-Zugriffe ändern sich nicht. Da könnte man wohl die "def"-Variante verwenden. Beim Züruckholen ins UI wird der Wert abgefangen und durch '' ersetzt. Nachteile: es wird ein falscher Wert geführt, der in den Rechnungen Probleme machen könnte. Da müsste man allerhand Unsinn abfangen. Die Datenbank enthält falsche Werte, die bei allen folgenden Abfragen/Rechnungen/etc. berücksichtigt werden müssten und letztlich womöglich eines Tages keiner weiß, warum ein Teil der Felder merkwürdige Zahlen enthält.

3. alle Eingaben grundsätzlich mal als String abspeichern. Dann ist mal die Originaleingabe erhalten. Für die Rechnung brauch ich die Umwandlung dennoch, dh die Variablenzahl verdoppelt sich. In der DB hab ich dann entweder nur noch strings und bin damit eingeschränkt oder alles doppelt. Einmal als string, einmal numerisch. Auch hier: der Aufwand ist groß, die Struktur in der Datenbank alles andere als elegant



Hab bestimmt was Eleganteres übersehen, oder?

danke

Gruß
cltom

Delphi.Narium 30. Apr 2022 14:09

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Unausgegorene Gedankensplitter von mir:
Delphi-Quellcode:
  // Schreiben in die DB:
  // statt
  if TryStrToFloat(Edit.Text, myFloat) then
    qry.FieldByName('Nummer').AsFloat := myFloat;
  // mal versuchen
  qry.FieldByName('Nummer').AsVariant = Edit.Text;
  // statt
  if TryStrToInt(Edit.Text, myInt) then
    qry.FieldByName('Nummer').AsInteger := myInt;
  // mal versuchen
  qry.FieldByName('Nummer').AsVariant := Edit.Text;

  // statt
  if TryStrToFloat(Edit.Text, myFloat) then
    qry.ParamByName('Nummer').AsFloat := myFloat;
  // mal versuchen
  qry.ParamByName('Nummer').Value = Edit.Text;
  // statt
  if TryStrToInt(Edit.Text, myInt) then
    qry.ParamByName('Nummer').AsInteger := myInt;
  // mal versuchen
  qry.ParamByName('Nummer').Value := Edit.Text;

  // Lesen aus der DB:
  Edit.Text := qry.FieldByName('Nummer').AsVariant;
Varianten machen eine implizite Typkonvertierung, wenn Edit.Text leer ist, könnte damit dann Null in die DB geschrieben werden. Müsstest Du mal prüfen, keine Ahnung, ob sich da alle Datenbank(schnittstell)en gleich verhalten.
Beim Lesen aus der DB wird der Inhalt des Datenbankfeldes bei der Zuweisung per .AsVariant in den Typ konvertiert, der links vom Gleichheitszeichen steht. Bei Edit.text also in einen String.

Die delphitypische Typsicherheit könnte dabei allerdings verloren gehen.

cltom 30. Apr 2022 14:41

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Danke Dir!

ok, wär ein Ansatz, danke Dir. Allerdings hab ich aktuell alle Variablen in einem Objekt und schick das Objekt an die SQL-Query, wo dann die Variablen geschrieben werden. Das UI hat niemals alle Werte gleichzeitig angezeigt, dh ich muss die Werte mal alle holen, im Objekt lagern und damit später die Query befüttern.

Aber vermutlich ließe sich Dein Gedanke weiterspinnen, ob man grundsätzlich auf Typensicherheit verzichten könnte? Erscheint mir auch ein hoher Preis, zumal ganz sicher irgendwo am Weg (es werden noch Diagramme gezeichnet, etc.) Probleme auftauchen könnten, wenn da einzelne Komponenten doch float/double/... sehen wollen.

jaenicke 30. Apr 2022 15:00

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Stichwort nullable types:
https://dalijap.blogspot.com/2020/05...m-managed.html

Damit kannst du einen Leerstring auch als Nullwert speichern und so auch in die DB schreiben.

Uwe Raabe 30. Apr 2022 15:13

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von cltom (Beitrag 1505308)
Allerdings hab ich aktuell alle Variablen in einem Objekt und schick das Objekt an die SQL-Query, wo dann die Variablen geschrieben werden.

Unterstütz das DAC eine interne Update-Funktionalität (wie z.B. TFDQuery das tut), dann kann man dem TIntegerField einfach über AsString den Wert mitgeben (AsVariant geht glaube ich nicht so gut). Ist der String leer, wird NULL an die Datenbank übergeben, andernfalls der entsprechende Wert. Umgekehrt wird beim Lesen über AsString ein NULL in der DB als Leerstring zurückgegeben. Damit wird in der DB schon mal zwischen NULL und 0 unterschieden und dies auch im Edit-Control entsprechend dargestellt und ausgewertet.

Leider ist der Aufwand deutlich höher, wenn man die Query direkt zusammenbaut oder über ein ORM-ähnliches System darauf zugreift. Letzteres würde ein ORM erfordern, das auch Nullable Types unterstützt.

Obwohl mittlerweile eher verpönt, unterstützen datensensitive Controls diese Anforderung schon ewig von Haus aus.

Delphi.Narium 30. Apr 2022 15:32

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Bei meinem Ansatz mit Variant geht die Typsicherheit mit Sicherheit flöten und Nebenwirkungen sind gerade bei Float (zufällig ohne Nachkommastellen) und Integer eher zu erwarten als auszuschließen.

Alternative:

Die Routine, die UI ins Objekt / DB schreibt, prüft ob das UI-Feld leer ist.
Delphi-Quellcode:
  if Edit.Text = '' then
    qry.FieldByName('Nummer').AsVariant := EmptyParam // Das müsste in der DB dann NULL werden.
  else
  begin
    if TryStrToFloat(Edit.Text, myFloat) then
      qry.FieldByName('Nummer').AsFloat := myFloat
    else
      // was ist hier im Fehlerfalle zu machen?
  end;
Bei leerem Edit.Text wird qry.FieldByName('Nummer').AsString := Edit.Text eher als Leerstring in der DB landen als als Null. Aber hier könnte es je nach Datenbank(schnittstelle) auch Unterschiede geben.

qry.FieldByName('Nummer').AsString := Edit.Text funktioniert allerdings auch, wenn Nummer in der DB als Integer oder Float definiert ist. Enthält Edit.Text allerdings etwas, was nicht zum Typ von Nummer passt, wird es eine Exception geben.

Uwe Raabe 30. Apr 2022 15:59

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1505313)
Bei leerem Edit.Text wird qry.FieldByName('Nummer').AsString := Edit.Text eher als Leerstring in der DB landen als als Null.

Zumindest bei einem TIntegerField wird es das eben nicht. Dort wird in SetAsString explizit ein Clear aufgerufen wenn der String leer ist. Das setzt allerdings voraus, dass in der Datenbank auch wirklich ein Integer Feld steht und kein String Feld. Aber das entnehme ich auch so der bisherigen Beschreibung.

cltom 30. Apr 2022 16:39

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Als DB-Schnittstelle kommt aktuell (noch) ZeosDB zum Einsatz, künftig vermutlich FireDAC (Update auf 11.1 steht auch ins Haus, dabei gleich mit FireDAC). Als zusätzlich Komplikation: aktuell wird noch in eine lokale SQLite geschrieben, bald mal auf einen SQL-Server.

Datensensitive Controls hab ich lange überlegt, dann aber nicht verwendet ... allen voran wohl weil ich nicht abschätzen konnte, wie aufwändig das werden würde, wenn ich viele der Felder für diverse Rechnungen brauche oder sich einige der Felder auch erst aus Berechnungen ergeben. Kann man sicher lösen, aber ich hatte mich nicht drüber getraut. Aktuell repräsentieren meine Objekte recht schön den realen Anwendungsfall.

Betreffend Nullable Types: ist es am Ende nicht genau das Szenario, dass man zu jeder Variable ein bool mitführt, das sagt, ob die Variable gesetzt wurde? Dann muss man beim Schreiben also den Status dieses Bools abfragen und dann entscheiden, ob man die Variable in den SQL Befehl aufnimmt. Nicht nur, dass man muss sie im Grunde auch mit in die SQL schreiben, um sicher zu gehen, dass beim Lesen aus der DB nicht erst recht wieder ein 0 als Ergebnis in die Variable kommt.

Delphi.Narium 30. Apr 2022 16:44

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1505314)
Zitat:

Zitat von Delphi.Narium (Beitrag 1505313)
Bei leerem Edit.Text wird qry.FieldByName('Nummer').AsString := Edit.Text eher als Leerstring in der DB landen als als Null.

Zumindest bei einem TIntegerField wird es das eben nicht. Dort wird in SetAsString explizit ein Clear aufgerufen wenn der String leer ist. Das setzt allerdings voraus, dass in der Datenbank auch wirklich ein Integer Feld steht und kein String Feld. Aber das entnehme ich auch so der bisherigen Beschreibung.

Du hast recht, bei 'nem Stringfeld in der DB wird qry.FieldByName('Nummer').AsString := Edit.Text eher als Leerstring in der DB landen als als Null.
Aber bei nummerischen Feldern in der DB ist dort ja letztlich auch kein Leersting in der DB möglich, also muss da "irgendwas anderes für das Feld passendes" gefunden werden. Und das ist bei Integer ... halt leider nicht Null sondern 0. Und damit sind wir dann bei unserem Ursprungsproblem.

Uwe Raabe 30. Apr 2022 17:03

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1505316)
Aber bei nummerischen Feldern in der DB ist dort ja letztlich auch kein Leersting in der DB möglich, also muss da "irgendwas anderes für das Feld passendes" gefunden werden. Und das ist bei Integer ... halt leider nicht Null sondern 0.

Diese Aussage wage ich zu bezweifeln. Auch ein Integer-Feld in der Datenbank kann natürlich NULL sein. Es ist allerdings so, dass TIntegerField über AsInteger oder Value in dem Fall 0 liefert. Was soll es auch anders tun, wenn ein Integer abgefragt wird. Deswegen hatte ich ja auch auf AsString für das Schreiben und Lesen hingewiesen. Das liefert nämlich bei NULL wieder einen Leerstring und nicht '0'.

Das Problem ist also nicht die DB oder das TEdit, auch nicht Zeos, FireDAC oder TIntegerField. Wenn das nicht funktioniert liegt der Fehler woanders.

Delphi.Narium 30. Apr 2022 17:18

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Das Integer in der DB Null sein kann ist klar, aber dazu muss dann die Schnittstelle auch Null liefern und nicht aus 'nem leeren String eine 0 machen.

Also ein Versuch:

Ein Leerstring per .AsString in ein nummerisches Feld der Datenbank, führt nicht zwingend zu einem Null in der Datenbank sondern (vermutlich) eher zu einer 0.

Ein Leerstring per .AsString in ein Stringfeld der Datenbank, führt nicht zwingend zu einem Null in der Datenbank sondern (vermutlich) eher zu einem Leerstring.

Null bekommt man eher per .AsVariant := EmptyParam in die Datenbank, dabei sollte der Feldtyp nicht von Belang sein.

So besser oder hab' ich was übersehen, unklar formuliert, ...? Bin mir da halt nicht so wirklich sicher.

Uwe Raabe 30. Apr 2022 22:08

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1505318)
Ein Leerstring per .AsString in ein nummerisches Feld der Datenbank, führt nicht zwingend zu einem Null in der Datenbank sondern (vermutlich) eher zu einer 0.

Vermutlich? Ich kann dir versichern, dass es nicht so ist - bestätigt durch Lesen der Sourcen und Ausprobieren. Kannst du gerne selbst prüfen.

Zitat:

Zitat von Delphi.Narium (Beitrag 1505318)
Ein Leerstring per .AsString in ein Stringfeld der Datenbank, führt nicht zwingend zu einem Null in der Datenbank sondern (vermutlich) eher zu einem Leerstring.

Zumindest bei Verwendung von FireDAC kann man das über ein Property der Connection steuern: TFDFormatOptions.StrsEmpty2Null

Delphi.Narium 1. Mai 2022 09:37

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1505326)
Zitat:

Zitat von Delphi.Narium (Beitrag 1505318)
Ein Leerstring per .AsString in ein nummerisches Feld der Datenbank, führt nicht zwingend zu einem Null in der Datenbank sondern (vermutlich) eher zu einer 0.

Vermutlich? Ich kann dir versichern, dass es nicht so ist - bestätigt durch Lesen der Sourcen und Ausprobieren. Kannst du gerne selbst prüfen.

Zitat:

Zitat von Delphi.Narium (Beitrag 1505318)
Ein Leerstring per .AsString in ein Stringfeld der Datenbank, führt nicht zwingend zu einem Null in der Datenbank sondern (vermutlich) eher zu einem Leerstring.

Zumindest bei Verwendung von FireDAC kann man das über ein Property der Connection steuern: TFDFormatOptions.StrsEmpty2Null

Da wir hier von ZeosDB reden, und es dort datenbankabhängig (leicht) unterschiedliche Implementierungen gibt, kann ich halt (pauschal leider) nicht mit absoluter Sicherheit sagen, welches Verhalten zu erwarten ist.

Und da ich kein XE2 habe und auch keine Sourcen dazu, kann ich nicht im Quellcode nachschauen und mit Sicherheit sagen, wie das Verhalten dort genau sein kann, soll, darf oder muss.

Bei meinem Delphi werden per ADO Integer bzw. Float per .AsString := '' zu Null, ein VarChar wird mit .AsString := '' zu einem Leerstring.

Ist das so ein klar definiertes Verhalten, das bei allen Dantenbankkomponenten zu erwarten ist?
Dann sollte das im Eingangspost beschriebene Problem mit der grundsätzlichen Nutzung von .AsString := Edit.Text bei Integer- bzw. Floatwerten zu lösen sein.

Uwe Raabe 1. Mai 2022 22:59

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1505331)
Ist das so ein klar definiertes Verhalten, das bei allen Dantenbankkomponenten zu erwarten ist?

Das das Verhalten bereits von TIntegerField in Data.DB implementiert wird, müsste eine andere Zugriffsschicht schon eine eigene TIntegerField-Komponente mitbringen, die das dann auch noch anders (und somit inkompatibel) löst.

freimatz 2. Mai 2022 06:51

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von jaenicke (Beitrag 1505309)
Stichwort nullable types:
https://dalijap.blogspot.com/2020/05...m-managed.html

Damit kannst du einen Leerstring auch als Nullwert speichern und so auch in die DB schreiben.

:thumb:
Gibts in Spring4D schon.

Frickler 2. Mai 2022 08:38

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Mach Dir nen eigenen Datentyp, ein record oder eine Klasse mit dem eigentlichen Wert und einem Flag "gesetzt" und die passenden Methoden dabei, u.a. ein Setter und ein "WriteToField(ds: TDataSet; const fn: string)", bei dem Du dann je nach "gesetzt" entweder den Wert übergibst oder das Feld unverändert lässt oder auch löschst.
Statt "TryStrToFloat" musst Du dann sowas wie "try StrToFloat... gesetzt := true; except .. end" nutzen. Vielleicht gleich als Methode mit in den eigenen Datentyp packen.

cltom 2. Mai 2022 08:50

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

Zitat von Frickler (Beitrag 1505351)
Mach Dir nen eigenen Datentyp, ein record oder eine Klasse mit dem eigentlichen Wert und einem Flag "gesetzt" und die passenden Methoden dabei, u.a. ein Setter und ein "WriteToField(ds: TDataSet; const fn: string)", bei dem Du dann je nach "gesetzt" entweder den Wert übergibst oder das Feld unverändert lässt oder auch löschst.
Statt "TryStrToFloat" musst Du dann sowas wie "try StrToFloat... gesetzt := true; except .. end" nutzen. Vielleicht gleich als Methode mit in den eigenen Datentyp packen.

den Teil kann ich mir noch gut vorstellen, aufwändig, aber denkbar, aber dann die SQL-Befehle basteln. Da muss man dann bei jedem einzelnen Wert erst mal prüfen, ob das Flag gesetzt ist. Da erscheint es fast leichter, das Flag einfach mitzuschreiben in die SQL, weil dann der SQL-string im Grunde unverändert bleibt. Es wird alles geschrieben (Null oder nicht), aber eben auch das Flag wird geschrieben, sodass ich beim lesen das wieder gesetzt hab.

oder seh ich das falsch an Deinem Ansatz? sonst ist die Idee ja schön mit dem Record, weil man das Verhalten dann auch standardisieren kann

Frickler 2. Mai 2022 09:22

AW: TryStrToFloat/TryStrToInt, leeren String nicht als 0 erhalten
 
Zitat:

den Teil kann ich mir noch gut vorstellen, aufwändig, aber denkbar, aber dann die SQL-Befehle basteln. Da muss man dann bei jedem einzelnen Wert erst mal prüfen, ob das Flag gesetzt ist. Da erscheint es fast leichter, das Flag einfach mitzuschreiben in die SQL, weil dann der SQL-string im Grunde unverändert bleibt. Es wird alles geschrieben (Null oder nicht), aber eben auch das Flag wird geschrieben, sodass ich beim lesen das wieder gesetzt hab.

oder seh ich das falsch an Deinem Ansatz? sonst ist die Idee ja schön mit dem Record, weil man das Verhalten dann auch standardisieren kann
Du brauchst in der Datenbank kein extra Flag. NULL ist schon das Flag.

Für alle leeren Felder schreibst Du NULL in die Tabelle. Für alle NULL Werte in der Tabelle liest Du ein leeres Feld. Bei Berechnungen in SQL kannst Du mit COALESCE() die NULL Werte in 0 wandeln, falls nötig.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:18 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz