Database
 sql >> Datenbank >  >> RDS >> Database

50 Schattierungen von NULL – Die verschiedenen Bedeutungen von NULL in SQL

Tony Hoare, der meist als Erfinder der NULL-Referenz bezeichnet wird, nennt sie jetzt einen milliardenschweren Fehler, unter dem inzwischen so ziemlich alle Sprachen „leiden“, einschließlich SQL.

Tony zitieren (aus seinem Wikipedia-Artikel):

Ich nenne es meinen Milliarden-Dollar-Fehler. Es war die Erfindung der Nullreferenz im Jahr 1965. Damals entwarf ich das erste umfassende Typsystem für Referenzen in einer objektorientierten Sprache (ALGOL W). Mein Ziel war es, sicherzustellen, dass die Verwendung von Referenzen absolut sicher ist, wobei die Überprüfung automatisch vom Compiler durchgeführt wird. Aber ich konnte der Versuchung nicht widerstehen, eine Nullreferenz einzubauen, einfach weil es so einfach zu implementieren war. Dies hat zu unzähligen Fehlern, Schwachstellen und Systemabstürzen geführt, die in den letzten vierzig Jahren wahrscheinlich Schmerzen und Schäden in Höhe von einer Milliarde Dollar verursacht haben.

Das Interessante hier ist, dass Tony versucht war, diese Referenz zu implementieren, weil es einfach war. Aber warum brauchte er überhaupt eine solche Referenz?

Die verschiedenen Bedeutungen von NULL

In einer perfekten Welt bräuchten wir NULL nicht. Jeder Mensch hat einen Vornamen und einen Nachnamen. Jeder Mensch hat ein Geburtsdatum, einen Beruf etc. Oder doch?

Leider nicht.

Nicht alle Länder verwenden das Konzept von Vor- und Nachnamen.

Nicht alle Menschen haben einen Job. Oder manchmal kennen wir ihren Job nicht. Oder es ist uns egal.

Hier ist NULL äußerst nützlich. NULL kann all diese Zustände modellieren, die wir eigentlich nicht modellieren wollen. NULL kann sein:

  • Der „undefinierte“ Wert , d. h. der Wert, der (wahrscheinlich aus technischen Gründen) noch nicht definiert ist, aber möglicherweise später definiert wird. Denken Sie an eine Person, die wir der Datenbank hinzufügen möchten, um sie in anderen Tabellen zu verwenden. Zu einem späteren Zeitpunkt fügen wir den Job dieser Person hinzu.
  • Der „unbekannte“ Wert , d. h. den Wert, den wir nicht kennen (und vielleicht nie kennen werden). Vielleicht können wir diese Person oder ihre Angehörigen nicht mehr nach ihrem Geburtsdatum fragen – die Informationen sind für immer verloren. Aber wir wollen trotzdem die Person modellieren, also verwenden wir NULL im Sinne von UNKNOWN (was seine wahre Bedeutung in SQL ist, wie wir später sehen werden).
  • Der „optionale“ Wert , d. h. der Wert, der nicht definiert werden muss. Beachten Sie, dass der Wert „optional“ auch bei einem OUTER JOIN erscheint, wenn der äußere Join keine Werte auf einer Seite der Beziehung erzeugt. Oder auch bei Verwendung von GROUPING SETS, wo verschiedene Kombinationen von GROUP BY-Spalten kombiniert (oder leer gelassen) werden.
  • Der Wert „gelöscht“ oder „vermieden“ , also den Wert, den wir nicht angeben wollen. Vielleicht registrieren wir normalerweise den Familienstand einer Person, wie es in einigen Gerichtsbarkeiten der Fall ist, aber nicht in anderen, wo es nicht legal ist, personenbezogene Daten dieser Art zu registrieren. Daher möchten wir diesen Wert in manchen Fällen nicht wissen.
  • Der „besondere“ Wert in einem bestimmten Kontext , also den Wert, den wir im Bereich möglicher Werte nicht anders modellieren können. Dies geschieht häufig bei der Arbeit mit Datumsbereichen. Nehmen wir an, der Job einer Person ist durch zwei Datumsangaben begrenzt, und wenn die Person derzeit in dieser Position arbeitet, verwenden wir NULL, um anzugeben, dass der Zeitraum am Ende des Datumsbereichs unbegrenzt ist.
  • Die „zufällige“ NULL , d.h. der NULL-Wert, der nur NULL ist, weil die Entwickler nicht aufgepasst haben. In Ermangelung einer expliziten NOT NULL-Einschränkung gehen die meisten Datenbanken davon aus, dass Spalten nullfähig sind. Und sobald Spalten nullfähig sind, könnten Entwickler einfach „versehentlich“ NULL-Werte in ihre Zeilen einfügen, wo sie es nicht einmal beabsichtigt hatten.

Wie wir oben gesehen haben, sind dies nur einige wenige der 50 Shades of NULL .

Das folgende Beispiel zeigt verschiedene Bedeutungen von NULL in einem konkreten SQL-Beispiel:




CREATE TABLE company (
    id int NOT NULL,
    name text NOT NULL,
    CONSTRAINT company_pk PRIMARY KEY (id)
);
CREATE TABLE job (
    person_id int NOT NULL,
    start_date date NOT NULL,

    -- If end_date IS NULL, the “special value” of an unbounded
    -- interval is encoded
    end_date date NULL,
    description text NOT NULL,

    -- A job doesn’t have to be done at a company. It is “optional”.
    company_id int NULL,
    CONSTRAINT job_pk PRIMARY KEY (person_id,start_date),
    CONSTRAINT job_company FOREIGN KEY (company_id) 
        REFERENCES company (id) 
);
CREATE TABLE person (
    id int  NOT NULL,
    first_name text NOT NULL,

    -- Some people need to be created in the database before we
    -- know their last_names. It is “undefined”
    last_name text NULL,

    -- We may not know the date_of_birth. It is “unknown”
    date_of_birth date NULL,

    -- In some situations, we must not define any marital_status.
    -- It is “deleted”
    marital_status int NULL,
    CONSTRAINT person_pk PRIMARY KEY (id),
    CONSTRAINT job_person FOREIGN KEY (person_id)
        REFERENCES person (id)
); 

Menschen haben immer darüber gestritten, dass es keinen Wert gibt

Wenn NULL ein so nützlicher Wert ist, warum wird er dann immer wieder kritisiert?

Alle diese früheren Anwendungsfälle für NULL (und andere) werden in diesem interessanten, kürzlich erschienenen Vortrag von C.J. Date über „Das Problem fehlender Informationen“ gezeigt (sehen Sie sich das Video auf YouTube an).

Modernes SQL kann viele großartige Dinge tun, die nur wenigen Entwicklern von Allzwecksprachen wie Java, C#, PHP nicht bewusst sind. Ich zeige dir weiter unten ein Beispiel.

In gewisser Weise stimmt C.J. Date Tony Hoare zu, dass die (Miss-)Verwendung von NULL für all diese verschiedenen Arten von „fehlenden Informationen“ eine sehr schlechte Wahl ist.

Beispielsweise werden in der Elektronik ähnliche Techniken angewendet, um Dinge wie 1, 0, „Konflikt“, „nicht zugewiesen“, „unbekannt“, „egal“, „hohe Impedanz“ zu modellieren. Beachten Sie jedoch, wie in der Elektronik verschiedene Sonderwerte werden für diese Dinge anstelle eines einzelnen speziellen NULL-Werts verwendet . Ist das wirklich besser? Was halten JavaScript-Programmierer von der Unterscheidung zwischen verschiedenen „falschen“ Werten wie „null“, „undefined“, „0“, „NaN“, dem leeren String „“? Ist das wirklich besser?

Apropos Null:Wenn wir den SQL-Raum für einen Moment verlassen und uns der Mathematik zuwenden, werden wir sehen, dass alte Kulturen wie die Römer oder die Griechen die gleichen Probleme mit der Zahl Null hatten. Tatsächlich hatten sie im Gegensatz zu anderen Kulturen nicht einmal die Möglichkeit, Null darzustellen, wie im Wikipedia-Artikel über die Zahl Null zu sehen ist. Zitat aus dem Artikel:

Aufzeichnungen zeigen, dass die alten Griechen sich über den Status der Null als Zahl unsicher zu sein schienen. Sie fragten sich:„Wie kann nichts etwas sein?“, was zu philosophischen und im Mittelalter religiösen Auseinandersetzungen über die Natur und Existenz der Null und des Vakuums führte.

Wie wir sehen, erstrecken sich die „religiösen Argumente“ eindeutig auf die Informatik und Software, wo wir immer noch nicht sicher wissen, was wir mit dem Fehlen eines Werts anfangen sollen.

Zurück zur Realität:NULL in SQL

Während sich die Menschen (einschließlich Akademiker) immer noch nicht darüber einig sind, ob wir eine Codierung für „undefiniert“, „unbekannt“, „optional“, „gelöscht“, „speziell“ benötigen, kehren wir zur Realität und den schlechten Seiten darüber zurück NULL von SQL.

Eine Sache, die beim Umgang mit NULL von SQL häufig vergessen wird, ist, dass es den UNKNOWN-Fall, der ein spezieller Wert ist, der Teil der sogenannten dreiwertigen Logik ist, formal implementiert, und zwar inkonsistent, z. im Fall von UNION- oder INTERSECT-Operationen.

Wenn wir zu unserem Modell zurückkehren:





Wenn wir beispielsweise alle Personen finden möchten, die nicht als verheiratet registriert sind, würden wir intuitiv die folgende Anweisung schreiben:

SELECT * FROM person WHERE marital_status != 'married'

Leider gibt die obige Abfrage aufgrund der dreiwertigen Logik und der NULL von SQL keine Werte zurück, die keinen expliziten marital_status haben. Daher müssen wir ein zusätzliches, explizites Prädikat schreiben:

SELECT * FROM person 
WHERE marital_status != 'married'
OR marital_status IS NULL

Oder wir zwingen den Wert vor dem Vergleichen auf einen NICHT-NULL-Wert

SELECT * FROM person
WHERE COALESCE(marital_status, 'null') != 'married'

Die dreiwertige Logik ist schwierig. Und das ist nicht das einzige Problem mit NULL in SQL. Hier sind weitere Nachteile der Verwendung von NULL:

  • Es gibt nur eine NULL, obwohl wir eigentlich mehrere verschiedene „abwesende“ oder „besondere“ Werte codieren wollten. Der Umfang sinnvoller Sonderwerte hängt stark von der Domäne und den verwendeten Datentypen ab. Dennoch ist immer Domänenwissen erforderlich, um die Bedeutung einer Nullable-Spalte richtig zu interpretieren, und Abfragen müssen sorgfältig entworfen werden, um zu verhindern, dass falsche Ergebnisse zurückgegeben werden, wie wir oben gesehen haben.
  • Auch hier ist die dreiwertige Logik sehr schwer richtig zu machen. Obwohl das obige Beispiel immer noch ziemlich einfach ist, was glauben Sie, wird die folgende Abfrage ergeben?
    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', NULL)
    

    Genau. Es wird überhaupt nichts ergeben, wie in diesem Artikel hier erklärt. Kurz gesagt, die obige Abfrage ist die gleiche wie die folgende:

    SELECT * FROM person 
    WHERE marital_status != 'married'
    AND marital_status != NULL -- This is always NULL / UNKNOWN
    
  • Die Oracle-Datenbank behandelt NULL und die leere Zeichenfolge '' als dasselbe. Dies ist sehr knifflig, da Sie nicht sofort bemerken werden, warum die folgende Abfrage immer ein leeres Ergebnis zurückgibt:

    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', '')
    

  • Oracle fügt (wieder) keine NULL-Werte in Indizes ein. Dies ist die Quelle vieler unangenehmer Leistungsprobleme, z. B. wenn Sie eine Nullable-Spalte in einem NOT IN-Prädikat als solches verwenden:

    SELECT * FROM person 
    WHERE marital_status NOT IN (
      SELECT some_nullable_column
      FROM some_table
    )
    

    Bei Oracle führt der obige Anti-Join zu einem vollständigen Tabellenscan, unabhängig davon, ob Sie einen Index für some_nullable_column haben. Aufgrund der dreiwertigen Logik und weil Oracle keine NULL-Werte in Indizes einfügt, muss die Engine auf die Tabelle zugreifen und jeden Wert überprüfen, nur um sicherzugehen, dass nicht mindestens ein NULL-Wert in der Menge vorhanden ist, was die ganzes Prädikat UNBEKANNT.

Schlussfolgerung

Wir haben das NULL-Problem in den meisten Sprachen und Plattformen noch nicht gelöst. Während ich behaupte, dass NULL NICHT der Milliarden-Dollar-Fehler ist, für den sich Tony Hoare zu entschuldigen versucht, ist NULL sicherlich auch weit davon entfernt, perfekt zu sein.

Wenn Sie mit Ihrem Datenbankdesign auf der sicheren Seite bleiben wollen, vermeiden Sie unbedingt NULL-Werte, es sei denn, Sie benötigen unbedingt einen dieser speziellen Werte, um mit NULL zu codieren. Denken Sie daran, diese Werte sind:„undefiniert“, „unbekannt“, „optional“, „gelöscht“ und „speziell“ und mehr:Die 50 Schattierungen von NULL . Wenn Sie sich nicht in einer solchen Situation befinden, fügen Sie standardmäßig immer eine NOT NULL-Einschränkung zu jeder Spalte in Ihrer Datenbank hinzu. Ihr Design wird viel sauberer und Ihre Leistung viel besser.

Wenn nur NOT NULL der Standard in DDL wäre und NULLABLE das Schlüsselwort, das explizit gesetzt werden müsste…

Was sind deine Takes und Erfahrungen mit NULL? Wie würde Ihrer Meinung nach ein besseres SQL funktionieren?

Lukas Eder ist Gründer und CEO der Data Geekery GmbH mit Sitz in Zürich, Schweiz. Data Geekery vertreibt seit 2013 Datenbankprodukte und Dienstleistungen rund um Java und SQL.

Seit seinem Masterstudium an der EPFL im Jahr 2006 fasziniert ihn das Zusammenspiel von Java und SQL. Die meisten dieser Erfahrungen hat er im Bereich Schweizer E-Banking durch verschiedene Varianten (JDBC, Hibernate, meist mit Oracle) gesammelt. Dieses Wissen gibt er gerne auf diversen Konferenzen, JUGs, Inhouse-Präsentationen und seinem Firmenblog weiter.