Der stille Saboteur NULL: Seine Auswirkungen auf SQL-Abfragen verstehen
Lukas Schneider
DevOps Engineer · Leapcell

Einleitung
In der Welt der relationalen Datenbanken sind Datenintegrität und präzise Abfragen von größter Bedeutung. Entwickler und Datenanalysten verbringen unzählige Stunden damit, präzise SQL-Anweisungen zu formulieren, um aussagekräftige Erkenntnisse zu gewinnen. Doch unter der Oberfläche scheinbar einfacher Abfragen lauert ein täuschend einfaches, aber äußerst komplexes Konzept: NULL. Oft missverstanden und häufig unterschätzt, kann NULL das erwartete Verhalten von COUNT(), JOIN und WHERE-Klauseln auf leise Weise sabotieren, was zu falschen Aggregationen, Datenverlust bei Joins und schwer auffindbaren Datensätzen führt. Dieser Artikel befasst sich mit den Nuancen von NULL und veranschaulicht, wie dieser spezielle Marker scheinbar einfache Abfragen in komplizierte Rätsel verwandeln kann, und bietet praktische Beispiele zur Beleuchtung seiner Auswirkungen.
Die besondere Natur von NULL
Bevor wir die Komplexitäten aufschlüsseln, wollen wir ein klares Verständnis von NULL schaffen.
- NULL: In SQL ist NULL eine Markierung für fehlende oder unbekannte Informationen. Es ist wichtig zu verstehen, dass NULL kein Wert ist. Es ist nicht Null, es ist keine leere Zeichenkette und es ist nicht falsch. Es ist die Abwesenheit eines Wertes. Dieser Unterschied ist grundlegend, da er beeinflusst, wie NULL mit Operatoren und Funktionen interagiert.
Mit diesem grundlegenden Verständnis wollen wir untersuchen, wie NULL gängige SQL-Operationen verkompliziert.
COUNT() und die Eigenheiten von NULL
Die Aggregatfunktion COUNT() wird verwendet, um die Anzahl der Zeilen oder Nicht-NULL-Werte in einer Spalte zu zählen. Das Vorhandensein von NULLs kann das Ergebnis erheblich verändern.
COUNT(*): Zählt alle Zeilen im Ergebnis mengensatz, einschließlich derjenigen mit NULL-Werten in einer beliebigen Spalte.COUNT(spaltenname): Zählt nur die Nicht-NULL-Werte in der angegebenenspaltenname.COUNT(DISTINCT spaltenname): Zählt die Anzahl eindeutiger Nicht-NULL-Werte in der angegebenenspaltenname.
Betrachten Sie eine produkte-Tabelle:
CREATE TABLE produkte ( produkt_id INT PRIMARY KEY, produkt_name VARCHAR(100), preis DECIMAL(10, 2), kategorie_id INT ); INSERT INTO produkte (produkt_id, produkt_name, preis, kategorie_id) VALUES (1, 'Laptop', 1200.00, 101), (2, 'Tastatur', 75.00, 102), (3, 'Maus', 25.00, 102), (4, 'Monitor', 300.00, NULL), (5, 'Webcam', 50.00, 103), (6, 'Lautsprecher', NULL, 103), (7, NULL, 10.00, 104); -- Stellen Sie sich ein Produkt mit unbekanntem Namen vor
Sehen wir uns COUNT() in Aktion an:
-- Alle Zeilen zählen SELECT COUNT(*) FROM produkte; -- Erwartete Ausgabe: 7 -- Nicht-NULL-Preise zählen SELECT COUNT(preis) FROM produkte; -- Erwartete Ausgabe: 6 (produkt_id 6 hat einen NULL-Preis) -- Nicht-NULL-kategorie_ids zählen SELECT COUNT(kategorie_id) FROM produkte; -- Erwartete Ausgabe: 6 (produkt_id 4 hat eine NULL-kategorie_id) -- Eindeutige kategorie_ids zählen (NULL wird nicht gezählt) SELECT COUNT(DISTINCT kategorie_id) FROM produkte; -- Erwartete Ausgabe: 3 (101, 102, 103. NULL ist ausgeschlossen) -- Nicht-NULL-produkt_namen zählen SELECT COUNT(produkt_name) FROM produkte; -- Erwartete Ausgabe: 6 (produkt_id 7 hat einen NULL-produkt_name)
Die Beispiele zeigen deutlich, dass COUNT(spaltenname) explizit NULLs ignoriert. Dies kann zu Verwirrung führen, wenn man erwartet, dass jede Zeile gezählt wird, in der diese Spalte existiert, unabhängig von ihrem Wert.
JOIN-Operationen und das schwer fassbare NULL
JOIN-Klauseln kombinieren Zeilen aus zwei oder mehr Tabellen basierend auf einer zugehörigen Spalte. Wenn diese zugehörigen Spalten NULLs enthalten, kann das Verhalten kontraintuitiv sein.
- NULL gleich NULL ist FALSCH: In SQL ergibt
NULL = NULLunbekannt, was in Vergleichsoperationen als FALSCH behandelt wird. Das bedeutet, dass eine Zeile mit einem NULL in der Join-Spalte niemals eine andere NULL in der Join-Spalte mit der Standardgleichheit (=) inINNER JOIN- oderLEFT/RIGHT JOIN-Bedingungen übereinstimmt.
Betrachten Sie eine kategorien-Tabelle:
CREATE TABLE kategorien ( kategorie_id INT PRIMARY KEY, kategorie_name VARCHAR(100) ); INSERT INTO kategorien (kategorie_id, kategorie_name) VALUES (101, 'Elektronik'), (102, 'Peripheriegeräte'), (103, 'Zubehör'), (999, 'Unkategorisiert'); -- Eine ungenutzte Kategorie
Lassen Sie uns nun produkte und kategorien verbinden:
-- INNER JOIN: Nur Zeilen, in denen produkt.kategorie_id mit kategorie.kategorie_id übereinstimmt SELECT p.produkt_name, c.kategorie_name FROM produkte p INNER JOIN kategorien c ON p.kategorie_id = c.kategorie_id;
Erwartete Ausgabe:
| produkt_name | kategorie_name |
|---|---|
| Laptop | Elektronik |
| Tastatur | Peripheriegeräte |
| Maus | Peripheriegeräte |
| Webcam | Zubehör |
| Lautsprecher | Zubehör |
Beachten Sie, dass 'Monitor' (mit kategorie_id NULL) und das Produkt mit produkt_name NULL (mit kategorie_id 104, die in kategorien nicht existiert) vollständig fehlen. Das liegt daran, dass produkt.kategorie_id = kategorie.kategorie_id für NULLs als unbekannt/falsch ausgewertet wird.
Um Zeilen aus der "linken" Tabelle (produkte) einzuschließen, auch wenn keine Übereinstimmung in der "rechten" Tabelle (kategorien) vorhanden ist, verwenden wir LEFT JOIN.
-- LEFT JOIN: Schließt alle Zeilen aus produkte ein, auch wenn keine Kategorieübereinstimmung vorhanden ist SELECT p.produkt_name, c.kategorie_name FROM produkte p LEFT JOIN kategorien c ON p.kategorie_id = c.kategorie_id;
Erwartete Ausgabe:
| produkt_name | kategorie_name |
|---|---|
| Laptop | Elektronik |
| Tastatur | Peripheriegeräte |
| Maus | Peripheriegeräte |
| Monitor | NULL |
| Webcam | Zubehör |
| Lautsprecher | Zubehör |
| NULL |
Hier erscheint 'Monitor', aber sein kategorie_name ist NULL, was auf keine Übereinstimmung hinweist. Das Produkt mit produkt_id 7 erscheint ebenfalls mit einem NULL kategorie_name, da kategorie_id 104 in der Tabelle kategorien nicht vorhanden ist. Wenn die kategorie_id von produkt_id 7 ebenfalls NULL wäre, würde es immer noch mit einem NULL kategorie_name erscheinen.
Wenn Sie speziell basierend auf der Anwesenheit von NULLs verbinden möchten, müssen Sie diese explizit mit IS NULL oder IS NOT NULL behandeln. Dies ist normalerweise keine gute Praxis für Joins, veranschaulicht aber das Konzept:
-- Dieser Join versucht explizit, NULLs abzugleichen, was die Standardgleichheit nicht tut SELECT p.produkt_name, c.kategorie_name FROM produkte p LEFT JOIN kategorien c ON (p.kategorie_id = c.kategorie_id) OR (p.kategorie_id IS NULL AND c.kategorie_id IS NULL); -- Dies würde nun potenziell eine NULL-kategorie_id in produkte mit einer NULL-kategorie_id in kategorien verbinden, -- vorausgesetzt, eine solche Zeile existierte in kategorien, was in unserem Beispiel nicht der Fall ist. -- In den meisten relationalen Designs wäre kategorie_id ein Fremdschlüssel und in der Kategorie-Tabelle nicht NULL.
Die wichtigste Erkenntnis für JOINs ist, dass NULLs in Join-Bedingungen bei Standardgleichheit keinen Treffer ergeben.
WHERE-Klausel und der rätselhafte NULL
Die WHERE-Klausel filtert Zeilen basierend auf angegebenen Bedingungen. NULLs in WHERE-Klauseln können zu unerwarteten Filterungen führen, da Standard-Vergleichsoperatoren (=, !=, <, >) bei der Berechnung mit NULL zu unbekannt werden.
spaltenname = NULList FALSCH (oder unbekannt): Diese Bedingung wird niemals wahr sein, was bedeutet, dassWHERE meine_spalte = NULLniemals Zeilen zurückgibt, selbst wennmeine_spalteNULLs enthält.spaltenname != NULList FALSCH (oder unbekannt): Ebenso wird auch dies niemals wahr sein.IS NULLundIS NOT NULL: Dies sind die richtigen Operatoren, um die Anwesenheit oder Abwesenheit von NULLs zu überprüfen.
Lassen Sie uns die produkte-Tabelle abfragen:
-- Versuch, Produkte mit NULL-Preisen zu finden (dies gibt nichts zurück) SELECT produkt_name, preis FROM produkte WHERE preis = NULL; -- Erwartete Ausgabe: (leerer Satz) -- Korrekter Weg, Produkte mit NULL-Preisen zu finden SELECT produkt_name, preis FROM produkte WHERE preis IS NULL; -- Erwartete Ausgabe: -- | produkt_name | preis | -- | Lautsprecher | NULL | -- Versuch, Produkte mit Nicht-NULL-Preisen zu finden (dies gibt nichts zurück) SELECT produkt_name, preis FROM produkte WHERE preis != NULL; -- Erwartete Ausgabe: (leerer Satz) -- Korrekter Weg, Produkte mit Nicht-NULL-Preisen zu finden SELECT produkt_name, preis FROM produkte WHERE preis IS NOT NULL; -- Erwartete Ausgabe: -- | produkt_name | preis | -- | Laptop | 1200.00| -- | Tastatur | 75.00 | -- | Maus | 25.00 | -- | Monitor | 300.00 | -- | Webcam | 50.00 | -- | NULL | 10.00 |
Darüber hinaus können NULL-Werte logische Operatoren in WHERE-Klauseln verkomplizieren:
-- Finde Produkte mit preis 75.00 ODER einer NULL-kategorie_id SELECT produkt_name, preis, kategorie_id FROM produkte WHERE preis = 75.00 OR kategorie_id IS NULL; -- Erwartete Ausgabe: -- | produkt_name | preis | kategorie_id | -- | Tastatur | 75.00 | 102 | -- | Monitor | 300.00| NULL | -- Finde Produkte mit preis 75.00 UND category_id ist NULL (dies gibt nichts zurück) SELECT produkt_name, preis, kategorie_id FROM produkte WHERE preis = 75.00 AND kategorie_id IS NULL; -- Erwartete Ausgabe: (leerer Satz)
Das Verhalten von AND und OR mit NULLs (insbesondere, wenn eine Seite als unbekannt ausgewertet wird) folgt der dreiwertigen Logik (WAHR, FALSCH, UNBEKANNT), die ohne ein solides Verständnis ihrer Regeln ziemlich komplex sein kann.
Best Practices für den Umgang mit NULLs
Um die durch NULL verursachten Komplexitäten zu mildern, beachten Sie diese Praktiken:
-
Verwenden Sie
IS NULLundIS NOT NULL: Verwenden Sie immer diese Operatoren, wenn Sie inWHERE-Klauseln nach dem Vorhandensein oder Fehlen von NULLs suchen. -
Verstehen Sie
COUNT()-Variationen: Seien Sie deutlich, ob Sie alle Zeilen zählen möchten (COUNT(*)) oder nur Nicht-NULL-Werte in einer Spalte (COUNT(spaltenname)). -
Behandeln Sie NULLs in Joins: Beachten Sie, dass Standard-Gleichheitsvergleiche NULLs nicht abgleichen. Wenn Sie NULLs in Join-Bedingungen behandeln müssen, benötigen Sie möglicherweise
COALESCEoder expliziteIS NULL-Prüfungen, obwohl dies oft auf ein potenzielles Designproblem hinweist. -
COALESCEundIFNULL(oderISNULL): Verwenden Sie diese Funktionen, um NULL-Werte durch einen Standardwert zu ersetzen und Vergleiche und Aggregationen vorhersehbar zu machen.-- Ersetzen Sie NULL-Preise durch 0 für die Aggregation SELECT produkt_name, COALESCE(preis, 0) AS tatsaechlicher_preis FROM produkte; -- Zähle Produkte, bei denen kategorie_id entweder 102 oder NULL ist (behandelt NULL als 'unbekannte Kategorie') SELECT COUNT(*) FROM produkte WHERE COALESCE(kategorie_id, -1) = 102 OR COALESCE(kategorie_id, -1) = -1; -
Datenbankdesign: Minimieren Sie NULLs, wo immer möglich. Wenn eine Spalte immer einen Wert haben soll, deklarieren Sie sie als
NOT NULL. Wenn ein Wert wirklich fehlen kann, verstehen Sie seine Auswirkungen.
Fazit
NULL ist weitaus mehr als nur "kein Wert"; es ist ein grundlegendes Konzept, das die Ausführung von SQL-Abfragen durchdringt und sorgfältige Überlegungen erfordert. Sein einzigartiges Verhalten in COUNT(), JOINs und WHERE-Klauseln kann scheinbar einfache Operationen in Quellen für subtile Fehler und falsche Ergebnisse verwandeln. Indem Entwickler die besondere Natur von NULL verstehen und geeignete SQL-Konstrukte wie IS NULL und COALESCE anwenden, können sie seine Komplexität beherrschen und die Genauigkeit und Zuverlässigkeit ihrer Datenbankabfragen sicherstellen. Der Schlüssel zur harmonischen Interaktion mit NULL liegt darin, seine Anwesenheit anzuerkennen und seine Regeln zu respektieren.