An dieser Stelle sind einige allgemeine Hinweise für Formeln sowie Unterschiede im Ergebnis von Formeln bei der Verwendung in Ansichten bzw. in Abhängigkeit des Datenbankservers aufgeführt.
Umgang mit nicht existierenden Objekten
Wird auf ein nicht existierendes Objekt zugegriffen, wird grundsätzlich Null
zurückgegeben und damit der komplette Ausdruck Null
.
Dieses Beispiel, um „EntityTitle Firma / EntityTitle Kontakt” auszugeben:
BAGetPrimarySource('<OrmCRMContact>', 'RelatedCompany', ?, ?, [EntityTitle]) + ' / ' + [EntityTitle]
gibt bei einer nicht vorhandenen Firma Null
aus.
Die Formel muss besser abgesichert werden, um dieses Verhalten zu vermeiden:
IsNull(BAGetPrimarySource('<OrmCRMContact>', 'RelatedCompany', ?, ?, [EntityTitle]), 'Keine Firma') + ' / ' + [EntityTitle]
Performance-Optimierung von Formeln mit vielen Datenbankabfragen
Formeln in einer Ansicht werden in genau eine Datenbankabfrage übersetzt und sind damit sehr performant. Werden Formeln außerhalb von Ansichten verwendet, können sie pro Formel zu mehreren Datenbankanfragen führen. Diese gilt es allerdings aus Performancegründen zu vermeiden.
Beispiel 1:
Werden in einer Maske zehn Felder auf Basis von Informationen aus einem über Relation zu ermittelnden Datensatz ausgeblendet, führt dies zu mindestens zehn Datenbankabfragen bei jedem Öffnen der Maske. Performanter wäre, die Felder in einer Gruppe zu organisieren und die Gruppe komplett auszublenden.
Beispiel 2:
Auch die Formel aus dem letzten Kapitel:
Iif(IsNull(BAGetPrimarySource('<OrmCRMContact>', 'RelatedCompany', ?, ?)), 'Keine Firma', BAGetPrimarySource('<OrmCRMContact>', 'RelatedCompany', ?, ?, [EntityTitle])) + ' / ' + [EntityTitle]
lässt sich für die Verwendung außerhalb von Ansichten optimieren zu:
IsNull(BAGetPrimarySource('<OrmCRMContact>', 'RelatedCompany', ?, ?), 'Keine Firma') + ' / ' + [EntityTitle]
Groß-/Kleinschreibung bei Textvergleichen
Wenn zwei Texte mit dem =
-Operator verglichen werden, hängt es vom Ausführungskontext und der verwendeten Datenbank ab, ob dabei die Groß-/Kleinschreibung berücksichtigt wird. Um definiertes Verhalten zu erreichen, verwenden sie die Funktion BAStrEquals.
Unterschiede bei ToStr()
Grundsätzlich kann die Umwandlung von Datentypen in eine Zeichenkette mit ToStr sowohl zwischen Ansichten und anderen Stellen aber auch innerhalb von Ansichten für unterschiedliche Datenbankserver unterschiedliche Ergebnisse liefern.
ToStr() ist quasi eine Konvertierung eines bestimmten Wertes in eine Text-Repräsentation. Die Stelle, an der diese Konvertierung durchgeführt wird, ist ein Aspekt, der das Ergebnis bestimmt. Besonders deutlich kann dies bei der Umwandlung eines Datumswertes (aber auch Zahlen mit entsprechenden Trennzeichen) werden (die Umwandlungen in den Beispielen dienen nur zur Illustration).
Wird die Funktion beispielsweise in einem berechneten Feld in einer Maske verwendet, so wird das Datum anhand der Benutzersprache (Locale) umgewandelt, so dass sich bei einem Benutzer mit deutscher Spracheinstellung beispielsweise “21.07.2022” ergibt. Diese Möglichkeit existiert bei der Nutzung in einer Ansicht nicht, da die Konvertierung in dem Fall nicht von der Applikation selbst sondern vom Datenbanksystem vorgenommen wird, d.h. hier liegt die Konvertierung nicht in unserer Hand und wir können nicht garantieren, dass die Konvertierung zwischen unterschiedlichen Datenbanksystemen (auch Versionen des gleichen Datenbanksystems) immer das gleiche Ergebnis liefert.
Aktuell ist das Ergebnis von ToStr() eines Datumswerts auf MSSQL und PostgreSQL identisch, die Funktion liefert ein Datum im ISO-Format (bsp: “2022-07-21 11:59:59.303385”). Offenbar spielen hier Regionseinstellungen des jeweiligen Servers nicht in die Konvertierung hinein.
Allerdings ist ToStr() ja nicht auf Datumswerte beschränkt, man könnte es beispielsweise auch zusammen mit Guids verwenden “ToStr([Oid])” … HIER sind die Ausgaben der unterschiedlichen Datenbanksysteme tatsächlich unterschiedlich: Auf MSSQL entsteht eine Guid mit Großbuchstaben, bei PostgreSQL eine mit Kleinbuchstaben. Also gibt es spätestens hier einen Unterschied. Weitere, wie beispielsweise mögliche Formatierungen von Nummern habe ich nicht geprüft.
Abschließend kann man also feststellen:
- Die Ergebnisse von ToStr() unterscheiden sich abhängig vom konvertierten Datentypen mit hoher Wahrscheinlichkeit bei dem Vergleich zwischen “Berechnete Spalte” in Ansichten und “Berechnetes Feld” in Masken oder anderen Berechnungen, die nicht auf einer Datenbankabfrage basieren.
- Beim Vergleich “Berechnete Spalte” in Ansichten gegen unterschiedliche Versionen des gleichen Datenbanksystems sind Unterschiede eher unwahrscheinlich, können aber natürlich in zukünftigen Versionen nicht ausgeschlossen werden.
- Beim Vergleich “Berechnete Spalte” in Ansichten gegen Datenbanksysteme unterschiedlicher Hersteller existieren nachweislich Unterschiede abhängig vom konvertieren Datentyp.
Vergleiche auf boolsche Spalten
Es wir empfohlen statt ![Spaltenname]
[Spaltenname] = false
zu schreiben. Das kann von der Datenbank besser optimiert werden.
Sematisch gibt es einen Unterschied, wenn der aktuelle Datensatz null ist, weil beispielsweise eine Relationsabfrage keinen Datensatz geliefert hat. Das ergibt in ersterer Variante true und in zweiter false.
Multiplikation und Division von Dezimalzahlen
Dezimalzahlen sind für Berechnungen mit Währungen vorgesehen, bei denen keine Rundungsfehler auftreten dürfen. Bei der Multiplikation oder Division kann es aber sehr leicht passieren, dass (Zwischen-)Ergebnisse so viele Dezimalstellen bekommen, dass sie nicht mehr exakt darstellbar sind. Das führt zu Laufzeitfehlern bei der Berechnung, vor allem bei PostgreSQL. Der Fehler tritt bereits auf, wenn das Ergebnis nicht darstellbar sein könnte, So ergibt z.B. die Multiplikation von 3 Dezimalzahlen mit 4 Nachkommastellen im allgemeinen 12 Nachkommastellen.
Bei Dezimalwert-Berechnungen sollte in der Formel angegeben werden, wie gerundet werden soll.
- Wenn das Ergebnis ohnehin nur ein Schätzwert ist (z.B. eine Prognose), dann sollte besser mit Gleitkommazahlen gerechnet werden. Die haben nicht den Exaktheitsanspruch und erzeugen keine Laufzeitfehler, auch nicht, wenn die Zahlen mal außerhalb des erwarteten Bereichs liegen.
Dies erreicht man, indem man in der Formel die FunktionToDouble([Dezimalwertspalte])
benutzt und dann komplett mit Gleitkommazahlen rechnet. - Wenn das Ergebnis wieder ein konkreter Geldbetrag mit festen Nachkommastellen werden soll (z.B. prozentualer Betrag), sollten das Ergebnis und ggf. auch Zwischenergebnisse in der Formel mit der Funktion
Round(..., 2)
gerundet werden.
Beachten Sie auch, dass Dezimalzahl-Konstanten wie bei * 1.19
in Formeln immer Gleitkommazahlen sind. Wenn man eine Berechnung mit Dezimalzahlen erzwingen will, muss man * 1.19m
schreiben.