TATECHATLAS
◎ Deutsch
Daten und Datenbanken / Tipp

Portierung von NULL- und Leerzeichen-Semantik zwischen Oracle, SQL Server, MySQL und SQLite

Portieren Sie die NULL-Behandlung zwischen Oracle, SQL Server, MySQL und SQLite, indem Sie vier Portabilitätspunkte fixieren: NULL-Tests, Vergleichsergebnisse, boolesche Kontexte und die Bedeutung leerer Zeichenfolgen.

Auf dieser Seite

Um die NULL-Behandlung zwischen Oracle, SQL Server, MySQL und SQLite zu portieren, müssen vier zentrale Portabilitätspunkte gelöst werden: wie ein NULL getestet wird, was ein Vergleich gegen NULL zurückgibt, ob boolesche Kontexte NULL erzwingen und was eine leere Zeichenfolge bedeutet. Das höchste Risiko stellt Oracle dar, da es derzeit einen Zeichenwert der Länge Null als NULL behandelt; die Oracle-Dokumentation warnt jedoch, dass dies in zukünftigen Versionen nicht mehr gelten könnte, und empfiehlt, leere Zeichenfolgen nicht wie NULLs zu behandeln. Verwenden Sie für Tests ausschließlich IS NULL und IS NOT NULL, da jede andere Bedingung mit NULL zu UNKNOWN auswertet. UNKNOWN verhält sich in WHERE-Klauseln fast wie FALSE, aber NOT UNKNOWN bleibt UNKNOWN. SQL Server bietet einen Konfigurations-Trade-off: Bei SET ANSI_NULLS ON ergeben ein oder zwei NULL-Operanden UNKNOWN, während bei ANSI_NULLS OFF die Operatoren = und <> NULL als bekannten Wert behandeln, der anderen NULLs entspricht, und nur TRUE oder FALSE zurückgeben, was die Filterergebnisse je nach Sitzungseinstellung ändert. MySQL lässt Vergleiche mit NULL als NULL zurückgeben, aber sein boolescher Kontext behandelt 0 oder NULL als false und alles andere als true. Zudem betrachtet MySQL zwei NULLs in GROUP BY als gleich und platziert NULLs bei ASC zuerst und bei DESC zuletzt. SQLite bietet kompakte IS- und IS NOT-Operatoren, die immer 1 oder 0 und niemals NULL zurückgeben, dokumentiert dies jedoch als SQLite-Erweiterung; Standard-SQL erfordert stattdessen IS NOT DISTINCT FROM und IS DISTINCT FROM. Der Trade-off liegt zwischen Konsistenz und Portabilität: Enginespezifische Formen sind lokal prägnant, während IS NULL / IS NOT NULL zusammen mit explizitem COALESCE-Handling die einzige Schreibweise ist, die überall vorhersehbar funktioniert. Erfassen Sie diese vier Punkte pro Engine in einer schriftlichen Matrix, anstatt davon auszugehen, dass Standard-SQL-Verhalten automatisch übernommen wird. Fügen Sie PostgreSQL als Zeile hinzu, die Sie gegen die eigene Dokumentation prüfen, da hierfür keine Quellen vorliegen.

Kontext und Empfehlung

Bevor Sie Abfragen oder gemeinsam genutzte SQL-Module zwischen Oracle, SQL Server, MySQL und SQLite portieren, erstellen Sie eine explizite Portabilitäts-Checkliste für NULL: (1) wie ein NULL getestet wird, (2) was ein Vergleich gegen NULL zurückgibt, (3) ob boolesche Kontexte NULL erzwingen und (4) was eine leere Zeichenfolge bedeutet. Der kritischste Punkt ist Oracle, das derzeit einen Zeichenwert der Länge Null als NULL behandelt, aber explizit empfiehlt, sich nicht darauf zu verlassen, dass leere Zeichenfolgen dasselbe wie NULLs sind. Dokumentieren Sie diese vier Punkte pro Engine in einer Matrix, anstatt davon auszugehen, dass das Standard-SQL-Verhalten übertragbar ist. Ergänzen Sie PostgreSQL als Zeile, die Sie anhand der offiziellen Dokumentation verifizieren.

Die Kernregel für die Portabilität lautet, NULL ausschließlich mit IS NULL und IS NOT NULL zu testen. Oracle gibt an, dass dies die einzigen zu verwendenden Vergleiche für NULLs sind. Jede andere Bedingung mit NULL ergibt UNKNOWN. UNKNOWN verhält sich in WHERE fast wie FALSE, aber NOT UNKNOWN bleibt UNKNOWN und wird nicht zu TRUE. SQL Server hat einen echten Konfigurations-Trade-off: Mit SET ANSI_NULLS ON ergeben ein oder zwei NULL-Operanden UNKNOWN. Mit ANSI_NULLS OFF behandeln die Operatoren = und <> NULL als bekannten Wert, der anderen NULLs entspricht, und geben nur TRUE oder FALSE zurück, was die Filterergebnisse je nach Sitzungseinstellung verändert. MySQL lässt Vergleiche mit NULL als NULL zurückgeben, aber sein boolescher Kontext behandelt 0 oder NULL als false und alles andere als true. Zudem werden zwei NULLs in GROUP BY als gleich betrachtet, während NULLs bei ASC zuerst und bei DESC zuletzt sortiert werden. SQLite bietet kompakte IS- und IS NOT-Operatoren, die immer 1 oder 0 und niemals NULL zurückgeben, was jedoch als SQLite-Erweiterung dokumentiert ist; Standard-SQL verlangt stattdessen IS NOT DISTINCT FROM und IS DISTINCT FROM.

Der Trade-off besteht zwischen Konsistenz und Portabilität: Enginespezifische Formen sind lokal prägnant und lesbar, während IS NULL / IS NOT NULL zusammen mit explizitem COALESCE-Handling die einzige Schreibweise ist, die überall vorhersehbar funktioniert. Gehen Sie nicht davon aus, dass eine für eine Engine geschriebene Abfrage unverändert auf einer anderen läuft, und nehmen Sie nicht an, dass eine leere Zeichenfolge und ein NULL austauschbar sind. Die Matrix sollte die vier Punkte pro Engine sowie die Sitzungseinstellungen erfassen, die Ergebnisse beeinflussen können, und als Checkliste für Code-Reviews dienen.

Nutzen Sie die Quellenbelege für die Matrix: Die Oracle-Dokumentation besagt, dass nur IS NULL und IS NOT NULL zu verwenden sind, andere Bedingungen zu UNKNOWN auswerten und dass Oracle derzeit Zeichenwerte der Länge Null als NULL behandelt, dies aber nicht empfohlen wird. Die SQL Server-Dokumentation besagt, dass bei SET ANSI_NULLS ON Operatoren mit NULL-Ausdrücken UNKNOWN zurückgeben, während sie bei ANSI_NULLS OFF NULL als bekannten Wert behandeln und nur TRUE oder FALSE zurückgeben. Die MySQL-Dokumentation zeigt, dass 1 = NULL, 1 <> NULL, 1 < NULL und 1 > NULL alles NULL zurückgeben, während 0 IS NULL, '' IS NULL als 0 und '' IS NOT NULL als 1 ausgewertet werden. Die SQLite-Dokumentation gibt an, dass IS- oder IS NOT-Ausdrücke niemals NULL auswerten können, dass die kompakten Formen eine Erweiterung sind und Standard-SQL IS NOT DISTINCT FROM sowie IS DISTINCT FROM erfordert.

-- portable across the engines covered here
SELECT
  '' IS NULL      AS empty_is_null,
  '' IS NOT NULL  AS empty_is_not_null,
  1 = NULL        AS eq_null,
  1 <> NULL       AS ne_null;
-- run the same probe twice on SQL Server: once with ANSI_NULLS ON, once OFF
SET ANSI_NULLS ON;
SELECT 1 = NULL;
SET ANSI_NULLS OFF;
SELECT 1 = NULL;
-- row filtering: only this form is portable
SELECT * FROM t WHERE col IS NOT NULL;

Überlegungen und Trade-offs

Die Engines stimmen in der Grundregel überein, weichen aber an den Rändern ab, was die Portabilität bricht. Oracle betont, dass IS NULL und IS NOT NULL die einzigen Vergleiche für NULLs sind und dass UNKNOWN in WHERE fast wie FALSE wirkt, aber bei einer Negation (NOT UNKNOWN) nicht zu TRUE wird. SQL Server bietet mit ANSI_NULLS eine Konfigurationsoption: Bei ON ergibt die Operation UNKNOWN, bei OFF werden NULLs als gleichwertige bekannte Werte behandelt, was die Filterergebnisse massiv beeinflusst. MySQL gibt bei Vergleichen mit NULL den Wert NULL zurück, erzwingt aber in booleschen Kontexten 0 oder NULL zu false. In GROUP BY werden zwei NULLs als gleich gewertet, und die Sortierung platziert NULLs bei ASC vorne und bei DESC hinten. SQLite nutzt IS und IS NOT als Erweiterungen für 1/0-Rückgabewerte, während Standard-SQL IS NOT DISTINCT FROM verlangt.

Der Trade-off liegt in der Wahl zwischen lokaler Prägnanz und globaler Portabilität. Enginespezifische Formen wie die von SQLite sind kurz, funktionieren aber nicht auf Systemen, die nur IS NOT DISTINCT FROM akzeptieren. Oracles Behandlung von leeren Zeichenfolgen als NULL ist kompakt, aber versionsabhängig und wird offiziell nicht empfohlen. Die ANSI_NULLS-Einstellung von SQL Server kann die Bedeutung von = und <> heimlich ändern, sodass eine scheinbar portable Abfrage je nach Sitzung unterschiedliche Zeilen zurückgibt. Die boolesche Erzwingung von MySQL betrifft nur boolesche Kontexte, nicht die Operatoren = und <>, die weiterhin NULL zurückgeben; eine Abfrage, die in WHERE funktioniert, könnte also in einem CASE-Ausdruck oder einem Funktionsargument scheitern.

Die Matrix sollte daher nicht nur die Ergebnisse von Tests, sondern auch die beeinflussenden Sitzungseinstellungen erfassen. Bei SQL Server sollte die tatsächliche ANSI_NULLS-Einstellung vor dem Test geprüft werden. Bei Oracle sollte die Version dokumentiert und nach Upgrades erneut getestet werden, da die Regel für leere Zeichenfolgen geändert werden könnte. Bei MySQL muss zwischen booleschen Kontexten und Vergleichsoperatoren unterschieden werden. Bei SQLite ist zu beachten, dass IS und IS NOT Erweiterungen sind. Zudem sollte die Matrix festhalten, ob die Engine IS NOT DISTINCT FROM unterstützt, da dies die portable Schreibweise für die Gleichheit von NULLs in Standard-SQL ist.

Die Empfehlung lautet, Abfragen mit IS NULL und IS NOT NULL zu schreiben und explizites COALESCE-Handling für potenziell NULL-Werte zu verwenden. Dies ist die einzige Methode, die unabhängig von der Behandlung leerer Zeichenfolgen, Sitzungseinstellungen oder booleschen Kontexten vorhersehbar funktioniert. Wenn eine Unterscheidung zwischen leerer Zeichenfolge und NULL nötig ist, sollten diese in separaten Spalten gespeichert oder Sentinel-Werte verwendet werden. Die Matrix dient als Checkliste für Code-Reviews und muss bei Versionsänderungen aktualisiert werden.

Konkrete Illustration

Führen Sie denselben kleinen Test auf jeder Ziel-Engine aus und protokollieren Sie die Ergebnisse: SELECT '' IS NULL, '' IS NOT NULL, 1 = NULL, 1 <> NULL, 0 = NULL; sowie einen Test für den booleschen Kontext wie SELECT NOT NULL. In Oracle sollte die leere Zeichenfolge wie NULL reagieren, während MySQL '' IS NULL als 0 und '' IS NOT NULL als 1 dokumentiert und 1 = NULL sowie 1 <> NULL als NULL zurückgibt. SQLite gibt bei IS und IS NOT 1 oder 0 statt NULL zurück. Testen Sie anschließend Zeilenfilter mit WHERE value <> NULL und WHERE value IS NOT NULL, um zu sehen, welche Zeilen tatsächlich zurückgegeben werden. Verwenden Sie eine Fixture-Tabelle, damit Unterschiede zwischen leeren Zeichenfolgen und NULLs explizit sichtbar werden.

Die Fixture-Tabelle sollte einen NULL, eine leere Zeichenfolge, eine Null und einen normalen Wert enthalten. In Oracle wird die leere Zeichenfolge als NULL gespeichert, sodass '' IS NULL als 1 und '' IS NOT NULL als 0 erscheint, während 1 = NULL und 1 <> NULL NULL zurückgeben. In MySQL sollte '' IS NULL als 0 und '' IS NOT NULL als 1 erscheinen, während 1 = NULL, 1 <> NULL, 1 < NULL und 1 > NULL alle NULL zurückgeben. In SQLite sollte '' IS NULL als 0 und '' IS NOT NULL als 1 erscheinen, während 1 = NULL und 1 <> NULL NULL zurückgeben und IS/IS NOT immer 1 oder 0 liefern. In SQL Server muss der Test zweimal laufen: einmal mit SET ANSI_NULLS ON und einmal mit OFF, da die Operatoren bei ON UNKNOWN und bei OFF TRUE oder FALSE zurückgeben.

Der Zeilenfilter-Test zeigt die tatsächlichen Ergebnisse. WHERE value <> NULL sollte in Oracle, MySQL und SQLite keine Zeilen zurückgeben, da der Vergleich NULL ergibt und UNKNOWN in WHERE fast wie FALSE wirkt. WHERE value IS NOT NULL sollte alle Nicht-NULL-Zeilen zurückgeben und ist die einzige portable Form. In SQL Server kann WHERE value <> NULL Zeilen zurückgeben, wenn ANSI_NULLS OFF ist, da NULL dann als bekannter Wert behandelt wird. Der boolesche Kontext-Test sollte zeigen, dass NOT NULL in Engines mit dreiwertiger Logik zu UNKNOWN auswertet und eine WHERE-Klausel mit UNKNOWN keine Zeilen zurückgibt, während ein Kontext, der UNKNOWN zu FALSE erzwingt, anders reagieren kann.

Tragen Sie die Ergebnisse in die Matrix ein, inklusive der SQL Server-Sitzungseinstellungen. Notieren Sie, ob IS NOT DISTINCT FROM unterstützt wird und ob die Regel für leere Zeichenfolgen in Oracle als potenziell änderbar dokumentiert ist. Diese Ergebnisse dienen als Beleg für die Portabilitäts-Checkliste und helfen bei der Entscheidung für die richtige Schreibweise in gemeinsamen SQL-Modulen. Die portable Lösung bleibt IS NULL / IS NOT NULL in Kombination mit COALESCE, da sie unabhängig von Engine-Besonderheiten, Einstellungen oder Kontexten ist.

Anwendbarkeitsgrenzen

Dieser Hinweis bezieht sich nur auf NULL-Vergleiche und die Semantik leerer Zeichenfolgen; er deckt keine aggregierten Zählungen von NULLs, Indexverhalten oder Performance ab. Da keine PostgreSQL-Dokumentation vorliegt, werden hier keine Aussagen zum Verhalten von PostgreSQL getroffen; dies muss vor einer Erweiterung der Matrix separat geprüft werden. Oracles Regel für Zeichenwerte der Länge Null ist als aktuelles Verhalten dokumentiert, das sich ändern kann; behandeln Sie dies als versionsabhängige Annahme. SQL Server-Ergebnisse hängen von ANSI_NULLS ab; prüfen Sie die tatsächliche Konfiguration statt Standardwerte anzunehmen. MySQLs boolesche Erzwingung von 0 und NULL zu false gilt nur für boolesche Kontexte, nicht für = und <> Vergleiche. SQLite-Formen von IS und IS NOT sind Erweiterungen und laufen nicht unverändert auf Engines, die nur IS NOT DISTINCT FROM akzeptieren.

Diese Grenzen sind wichtig, da die Matrix eine Checkliste und keine Garantie ist. Da Oracles Regel für leere Zeichenfolgen in Zukunft geändert werden kann, könnte eine heute funktionierende Abfrage nach einem Upgrade brechen. Die ANSI_NULLS-Einstellung in SQL Server kann die Bedeutung von = und <> ändern, was zu unterschiedlichen Ergebnissen je nach Sitzung führt. MySQLs boolesche Erzwingung kann dazu führen, dass eine Abfrage in einer WHERE-Klausel funktioniert, aber in einem CASE-Ausdruck oder einem Funktionsargument scheitert. Die kompakten IS-Formen von SQLite sind nicht portabel auf Standard-SQL-Engines.

Die Matrix sollte festhalten, ob IS NOT DISTINCT FROM unterstützt wird, da dies die portable Schreibweise für NULL-Gleichheit in Standard-SQL ist. Falls nicht unterstützt, ist die portable Lösung IS NULL / IS NOT NULL mit COALESCE. Die Matrix darf nicht dazu verwendet werden, Verhalten von einer Engine auf eine andere zu schließen oder anzunehmen, dass leere Zeichenfolgen und NULL austauschbar sind. Die Testergebnisse dienen als Beleg für die Entscheidung der Schreibweise in gemeinsamen Modulen.

Da keine PostgreSQL-Quellen vorliegen, muss das Verhalten dieses Systems separat verifiziert werden. Die Matrix sollte als Checkliste für Code-Reviews dienen und bei jeder Änderung des dokumentierten Verhaltens durch neue Engine-Versionen aktualisiert werden. Die portable Schreibweise IS NULL / IS NOT NULL mit COALESCE bleibt die sicherste Wahl, da sie unabhängig von der Behandlung leerer Zeichenfolgen, Sitzungseinstellungen oder booleschen Kontexten ist.

Anwendungsbedingungen

  • Behandelt die Ziel-Engine '' IS NULL als 0 und '' IS NOT NULL als 1, oder wird die leere Zeichenfolge zu NULL kollabiert?
  • Gibt ein Vergleich wie 1 = NULL den Wert NULL, UNKNOWN oder TRUE/FALSE zurück, abhängig von der Sitzungseinstellung?
  • Erzwingt ein boolescher Kontext wie WHERE NOT NULL oder IF(NOT NULL) NULL zu FALSE oder bleibt UNKNOWN erhalten?
  • Platziert ORDER BY NULLs bei ASC zuerst und bei DESC zuletzt, oder gilt eine andere Regel?
  • Behandelt GROUP BY zwei NULLs als gleich und unterstützt die Engine IS NOT DISTINCT FROM?
  • Ist die Regel 'leere Zeichenfolge als NULL' als aktuelles Verhalten dokumentiert, das sich in Zukunft ändern kann?
  • Ist die kompakte IS- und IS NOT-Syntax als Erweiterung und nicht als Standard-SQL dokumentiert?
  • Werden die Ergebnisse durch eine Sitzungseinstellung wie ANSI_NULLS beeinflusst und kann diese vor dem Test ausgelesen werden?
  • Unterscheidet die Testumgebung zwischen einem gespeicherten NULL und einer gespeicherten leeren Zeichenfolge?
  • Wurde die Matrix gegen die PostgreSQL-Dokumentation geprüft, bevor sie auf diese Engine erweitert wurde?

Dieser Hinweis deckt nur NULL-Vergleiche und die Semantik leerer Zeichenfolgen ab; aggregierte Zählungen, Indexverhalten oder Performance sind nicht enthalten. Es liegen keine PostgreSQL-Quellen vor, daher muss dessen Verhalten separat geprüft werden. Oracles Regel für Zeichenwerte der Länge Null ist versionsabhängig und kann sich ändern. SQL Server-Ergebnisse hängen von der ANSI_NULLS-Einstellung ab. MySQLs boolesche Erzwingung gilt nur für boolesche Kontexte, nicht für = und <> Vergleiche. SQLite-IS-Formen sind Erweiterungen und nicht standardmäßig portabel.

Quellen

  1. Oracle Database: Nulls ↗
  2. Microsoft SQL Server: Comparison Operators ↗
  3. MySQL 8.4: Working with NULL ↗
  4. SQLite: SQL Language Expressions ↗
  5. PostgreSQL: Comparison Functions and Operators ↗
Nach oben ↑