NULL in CDS ABAP
Veröffentlicht am 8. März 2024 von | ABAP | CDS |
Im SQL verhält sich NULL
wie ein schwarzes Loch. Es hält sich nicht an die "normalen" Regeln der boolschen Logik und saugt ganze Ausdrücke auf.
Und NULL
kann auch bei der Ausführung von CDS ABAP entstehen. Aber ABAP kommt damit nicht zurecht und übersetzt diese in den Initialwert. Das ist zwar manchmal praktisch, aber dadurch verlieren die Entwickler das Gefühl für den NULL
-Wert.
Was ist NULL?
"In SQL, null or NULL is a special marker used to indicate that a data value does not exist in the database. Introduced by the creator of the relational database model, E. F. Codd, SQL null serves to fulfil the requirement that all true relational database management systems (RDBMS) support a representation of "missing information and inapplicable information". "
Also soll NULL
nicht ein echter Wert sein, sondern das Fehlen eines Wertes anzeigen. Somit entspricht NULL
in der Datenbank auch nicht dem Initialwert.
NULL frisst Ausdrücke auf
Wenn ein Ausdruck einen NULL
Wert enthält, dann ist das Ergebnis ebenfalls NULL
.
{
NetAmount * ( Vat / 100 + 1 ) as GrossAmount
}
Wenn in dem Beispiel also AMOUNT
oder VAT
den Wert NULL
hat, dann ist GrossAmount
ebenfalls NULL
. Gleiches gilt beispielsweise auch für
- Verkettung von Zeichenketten
- Aufruf von SQL-Funktionen
CASE
Ausdrücke
Da die Konvertierung von NULL
in INITIAL
erst im ABAP passiert, ist die Logik von den Ausdrücken eine andere. Im folgenden Beispiel würde man vielleicht erwarten: Wenn der Text nicht gefunden wird, dann steht in dem Feld StatusText
der Wert "Status:". Statt dessen wird der ganze Ausdruck NULL
und damit im ABAP leer.
define view entity zbc_demo_null_in_expression
as select from zbc_tasks as t
left outer join zbc_status_text as st on t.status = st.status
and st.language = $session.system_language
{
t.task_key as TaskKey,
t.status as Status,
CONCAT_WITH_SPACE( 'Status: ', st.text, 1 ) as StatusText
}
TaskKey | Status | StatusText |
---|---|---|
INT-239 | NEW | Status: New |
R3N-200 | XYZ | |
INT-472 | NEW | Status: New |
NULL lässt sich (fast) nicht vergleichen
Der Vergleich mit NULL
ergibt im SQL immer den logischen Wert UNKNOWN
. Der ist also weder TRUE
noch FALSE
. An den folgenden Stellen geht es immer nur darum, dass ob ein Prädikat wahr ist, also den Wert TRUE
liefert:
WHERE
-KlauselON
-Bedingung im joinCASE WHEN
-Bedingung
Eine Unterscheidung zwischen UNKNOWN
und FALSE
findet nicht statt. Bei der Negierung dieser Werte wird das Problem deutlich:
NOT TRUE
ergibtFALSE
NOT FALSE
ergibtTRUE
NOT UNKNOWN
ergibtUNKNOWN
Das folgende Beispiel zeigt das Dilemma. In einer Datenbanktabelle stehen die folgenden Daten:
ID | Name |
---|---|
1 | Peter |
2 | Paul |
3 | Petra |
4 | Andrea |
5 | NULL |
Möchte ich alle Datensätze finden, deren Name mit P beginnt, dann liefert mir die folgende Abfrage das gewünschte Ergebnis:
SELECT *
FROM Tabelle
WHERE Name LIKE 'P%'
ID | Name |
---|---|
1 | Peter |
2 | Paul |
3 | Petra |
Wenn ich jetzt aber alle Datensätze suche, deren Name nicht mit P beginnt, dann liefert mir die folgende Abfrage nur den 4. Datensatz:
SELECT *
FROM Tabelle
WHERE Name NOT LIKE 'P%'
ID | Name |
---|---|
4 | Andrea |
Der 5. Datensatz wird also von keiner der beiden Abfragen gefunden.
Das IS NULL
-Prädikat
Leider funktioniert auch ein normaler Vergleich mit NULL
nicht: NAME = NULL
liefert immer UNKNOWN
. Denn es spielt keine Rolle, ob NULL
links oder rechts vom Vergleichsoperator steht.
Das einzige Prädikat, dass einen NULL
finden kann, heisst IS NULL
. Und das lässt sich auch bei Bedarf mit NOT
negieren.
SELECT *
FROM Tabelle
WHERE Name NOT LIKE 'P%'
OR Name IS NULL
ID | Name |
---|---|
4 | Andrea |
5 | NULL |
Wo kommt NULL her?
In der Datenbank des ABAP Systems kommt normalerweise kein NULL
vor. Die einzige mir bekannte Methode, um NULL
in eine Spalte zu bekommen besteht darin, dass man nachträglich Spalten hinzufügt und dabei nicht das Flag für Initialwerte setzt. Aber wo kann das noch passieren?
OUTER JOIN
Bei einem OUTER JOIN
werden immer dann NULL
verwendet, wenn für eine Zeile kein Partner in der anderen Tabelle gefunden werden kann.
CASE
ohne ELSE
Falls bei einem CASE
-Ausdruck keine Bedingung wahr ist und kein Wert für ELSE
definiert wurde, dann liefert der CASE
Ausdruck NULL
zurück.
Und wo ist das Problem mit ABAP?
Abgesehen von den besprochenen Punkten, die man vielleicht als ABAP Entwickler nicht unbedingt erwartet, gibt es ein ganz konkretes Problem bei der Umwandlung von NULL
in INITIAL
. Und das tritt beim Aggregieren auf. Wenn nach einer Spalte gruppiert werden soll, dann erzeugt die Datenbank für jeden unterschiedlichen Wert eine Zeile. Für die Datenbank sind NULL
und INITIAL
zwei unterschiedliche Werte. Die werden aber im ABAP auf den gleichen Wert gemappt.
define view entity zbc_demo_null_in_expression
as select from zbc_tasks as t
left outer join zbc_status_text as st on t.status = st.status
and st.language = $session.system_language
{
st.text as StatusText,
count(*) as cnt
}
group by st.text
Ergibt:
StatusText | cnt |
---|---|
Neu | 31 |
In Bearbeitung | 7 |
12 | |
34 |
Interpretation des Ergebnis:
- In 31 Zeilen steht der Text "Neu"
- In 7 Zeilen steht der Text "In Bearbeitung"
- Bei 12 Zeilen wurde in der Texttabelle kein passende Zeile gefunden. Der Wert wird auf
NULL
gesetzt. - Bei 34 Zeilen wurde in der Texttabelle ein Eintrag gefunden. Der Wert des
StatusText
ist dort aber leer gewesen. Es kann aber auch anders herum sein, denn man sieht dem Initialwert ja nicht an, woraus er gebildet wurde.
Fazit
Die Logik vom SQL entspricht nicht der Erfahrungswelt von einem ABAP Entwickler. Aber im CDS ABAP hat man relativ selten mit Problemen mit NULL
zu tun. Dann ist man aber lange am suchen, wenn man das Konzept von NULL
nicht verstanden hat.