sql >> Database >  >> RDS >> Database

50 Shades of NULL – De verschillende betekenissen van NULL in SQL

Tony Hoare, die meestal wordt aangeduid als de uitvinder van de NULL-referentie, noemt het nu een fout van een miljard dollar waar vrijwel alle talen nu "last" van hebben, inclusief SQL.

Tony citerend (uit zijn Wikipedia-artikel):

Ik noem het mijn fout van een miljard dollar. Het was de uitvinding van de nulreferentie in 1965. In die tijd ontwierp ik het eerste uitgebreide typesysteem voor verwijzingen in een objectgeoriënteerde taal (ALGOL W). Mijn doel was om ervoor te zorgen dat al het gebruik van referenties absoluut veilig zou zijn, waarbij de controle automatisch door de compiler werd uitgevoerd. Maar ik kon de verleiding niet weerstaan ​​om een ​​nulreferentie in te voeren, simpelweg omdat het zo gemakkelijk te implementeren was. Dit heeft geleid tot talloze fouten, kwetsbaarheden en systeemcrashes, die de afgelopen veertig jaar waarschijnlijk een miljard dollar aan pijn en schade hebben veroorzaakt.

Het interessante hier is dat Tony in de verleiding kwam om die verwijzing te implementeren omdat het gemakkelijk was om te doen. Maar waarom had hij zo'n referentie eigenlijk nodig?

De verschillende betekenissen van NULL

In een perfecte wereld zouden we NULL niet nodig hebben. Ieder mens heeft een voornaam en een achternaam. Iedereen heeft een geboortedatum, een baan, enz. Of toch?

Helaas doen ze dat niet.

Niet alle landen gebruiken het concept van voor- en achternaam.

Niet alle mensen hebben een baan. Of soms kennen we hun werk niet. Of het maakt ons niet uit.

Dit is waar NULL uiterst nuttig is. NULL kan al deze toestanden modelleren die we niet echt willen modelleren. NULL kan zijn:

  • De "ongedefinieerde" waarde , d.w.z. de waarde die nog niet is gedefinieerd (waarschijnlijk om technische redenen), maar die later wel kan worden gedefinieerd. Denk aan een persoon die we aan de database willen toevoegen om deze in andere tabellen te gebruiken. In een later stadium zullen we de baan van die persoon toevoegen.
  • De "onbekende" waarde , d.w.z. de waarde die we niet kennen (en misschien nooit zullen weten). Misschien kunnen we deze persoon of hun familieleden niet langer naar hun geboortedatum vragen - de informatie zal voor altijd verloren gaan. Maar we willen nog steeds de persoon modelleren, dus gebruiken we NULL in de zin van UNKNOWN (wat de ware betekenis is in SQL, zoals we later zullen zien).
  • De “optionele” waarde , d.w.z. de waarde die niet hoeft te worden gedefinieerd. Merk op dat de "optionele" waarde ook verschijnt in het geval van een OUTER JOIN, wanneer de outer join geen waarden produceert aan één kant van de relatie. Of ook bij het gebruik van GROUPING SETS, waarbij verschillende combinaties van GROUP BY-kolommen worden gecombineerd (of leeg gelaten).
  • De "verwijderde" of "vermijdde" waarde , d.w.z. de waarde die we niet willen specificeren. Misschien registreren we gewoonlijk de burgerlijke staat van een persoon, zoals in sommige rechtsgebieden wordt gedaan, maar niet in andere, waar het niet legaal is om dit soort persoonlijke gegevens te registreren. Daarom willen we deze waarde in sommige gevallen niet weten.
  • De "speciale" waarde in een bepaalde context , d.w.z. de waarde die we niet anders kunnen modelleren in het bereik van mogelijke waarden. Dit wordt vaak gedaan bij het werken met datumbereiken. Laten we aannemen dat de baan van een persoon wordt begrensd door twee datums, en als de persoon momenteel in die positie werkt, gebruiken we NULL om te zeggen dat de periode onbegrensd is aan het einde van het datumbereik.
  • De "toevallige" NULL , d.w.z. de NULL-waarde die gewoon NULL is omdat ontwikkelaars niet hebben opgelet. Bij afwezigheid van een expliciete NOT NULL-beperking, gaan de meeste databases ervan uit dat kolommen nullable zijn. En zodra kolommen nullable zijn, kunnen ontwikkelaars "per ongeluk" NULL-waarden in hun rijen plaatsen, waar ze dat niet eens van plan waren.

Zoals we hierboven hebben gezien, zijn dit slechts een select aantal van de 50 Shades of NULL .

Het volgende voorbeeld toont verschillende betekenissen van NULL in een concreet SQL-voorbeeld:




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)
); 

Mensen hebben altijd ruzie gemaakt over de afwezigheid van een waarde

Als NULL zo'n bruikbare waarde is, waarom blijven mensen er dan kritiek op hebben?

Al deze eerdere use-cases voor NULL (en andere) worden weergegeven in deze interessante, recente talk van C.J. Date over "The Problem of Missing Information" (bekijk de video op YouTube).

Moderne SQL kan veel geweldige dingen doen waarvan maar weinig ontwikkelaars van algemene talen zoals Java, C# en PHP zich niet bewust zijn. Ik zal je verderop een voorbeeld laten zien.

In zekere zin is C.J. Date het met Tony Hoare eens dat het (ab)gebruiken van NULL voor al deze verschillende soorten "ontbrekende informatie" een zeer slechte keuze is.

In de elektronica worden bijvoorbeeld vergelijkbare technieken toegepast om zaken als 1, 0, "conflict", "niet-toegewezen", "onbekend", "maakt niet uit", "hoge impedantie" te modelleren. Merk echter op hoe in de elektronica verschillende speciale waarden worden voor deze dingen gebruikt, in plaats van een enkele speciale NULL-waarde . Is dit echt beter? Hoe denken JavaScript-programmeurs over het onderscheid tussen verschillende "falsy" -waarden, zoals "null", "undefined", "0", "NaN", de lege string ''? Is dit echt beter?

Over nul gesproken:als we de SQL-ruimte even verlaten en in wiskunde gaan, zullen we zien dat oude culturen zoals de Romeinen of de Grieken dezelfde problemen hadden met het getal nul. Ze hadden zelfs geen enkele manier om nul weer te geven, in tegenstelling tot andere culturen, zoals te zien is in het Wikipedia-artikel over het getal nul. Citaat uit het artikel:

Uit gegevens blijkt dat de oude Grieken onzeker leken over de status van nul als getal. Ze vroegen zich af:"Hoe kan niets iets zijn?", wat leidde tot filosofische en, in de Middeleeuwen, religieuze argumenten over de aard en het bestaan ​​van nul en het vacuüm.

Zoals we kunnen zien, strekken de 'religieuze argumenten' zich duidelijk uit tot informatica en software, waar we nog steeds niet zeker weten wat we moeten doen met het ontbreken van een waarde.

Terug naar de realiteit:NULL in SQL

Terwijl mensen (inclusief academici) het nog steeds niet eens zijn over het feit of we codering nodig hebben voor "undefined", "unknown", "optioneel", "deleted", "special", laten we teruggaan naar de realiteit en de slechte delen over SQL's NULL.

Een ding dat vaak wordt vergeten bij het omgaan met de NULL van SQL, is dat het formeel het UNKNOWN-geval implementeert, wat een speciale waarde is die deel uitmaakt van de zogenaamde driewaardenlogica, en het doet dit, inconsequent, b.v. in het geval van UNION- of INTERSECT-operaties.

Als we teruggaan naar ons model:





Als we bijvoorbeeld alle mensen willen vinden die niet als gehuwd geregistreerd staan, zouden we intuïtief de volgende verklaring willen schrijven:

SELECT * FROM person WHERE marital_status != 'married'

Helaas, vanwege driewaardige logica en SQL's NULL, retourneert de bovenstaande query niet die waarden die geen expliciete burgerlijke_status hebben. Daarom moeten we een extra, expliciet predikaat schrijven:

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

Of we dwingen de waarde tot een NIET NULL-waarde voordat we deze vergelijken

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

Drie waarden logica is moeilijk. En het is niet het enige probleem met NULL in SQL. Hier zijn nog meer nadelen van het gebruik van NULL:

  • Er is maar één NULL, terwijl we echt verschillende "afwezige" of "speciale" waarden wilden coderen. Het bereik van nuttige speciale waarden is sterk afhankelijk van het domein en de gegevenstypen die worden gebruikt. Toch is domeinkennis altijd vereist om de betekenis van een nullable-kolom correct te interpreteren, en query's moeten zorgvuldig worden ontworpen om te voorkomen dat de verkeerde resultaten worden geretourneerd, zoals we hierboven hebben gezien.
  • Nogmaals, driewaardige logica is erg moeilijk om goed te krijgen. Hoewel het bovenstaande voorbeeld nog steeds vrij eenvoudig is, wat denkt u dat de volgende zoekopdracht zal opleveren?
    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', NULL)
    

    Precies. Het levert helemaal niets op, zoals hier in dit artikel wordt uitgelegd. Kortom, de bovenstaande vraag is dezelfde als de onderstaande:

    SELECT * FROM person 
    WHERE marital_status != 'married'
    AND marital_status != NULL -- This is always NULL / UNKNOWN
    
  • De Oracle-database behandelt NULL en de lege string '' als hetzelfde. Dit is erg lastig omdat je niet meteen zult merken waarom de volgende zoekopdracht altijd een leeg resultaat retourneert:

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

  • Oracle plaatst (opnieuw) geen NULL-waarden in indexen. Dit is de oorzaak van veel vervelende prestatieproblemen, bijvoorbeeld wanneer u een kolom met nullwaarden gebruikt in een NOT IN-predikaat als zodanig:

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

    Met Oracle zal de bovenstaande anti-join resulteren in een volledige tabelscan, ongeacht of je een index hebt op een_nullable_column. Vanwege logica met drie waarden en omdat Oracle geen NULL-waarden in indexen plaatst, moet de engine de tabel raken en elke waarde controleren om er zeker van te zijn dat er niet ten minste één NULL-waarde in de set zit, waardoor de hele predikaat ONBEKEND.

Conclusie

In de meeste talen en platforms hebben we het NULL-probleem nog niet opgelost. Hoewel ik beweer dat NULL NIET de fout van een miljard dollar is waarvoor Tony Hoare zich probeert te verontschuldigen, is NULL ook zeker verre van perfect.

Als u aan de veilige kant wilt blijven met uw database-ontwerp, vermijd dan ten koste van alles NULL's, tenzij u absoluut een van die speciale waarden nodig hebt om te coderen met NULL. Onthoud dat deze waarden zijn:"undefined", "unknown", " optional", "deleted" en "special", en meer:​​The 50 Shades of NULL . Als u zich niet in een dergelijke situatie bevindt, voeg dan altijd standaard een NOT NULL-beperking toe aan elke kolom in uw database. Je ontwerp zal veel schoner zijn en je prestaties veel beter.

Als alleen NOT NULL de standaard was in DDL, en NULLABLE het sleutelwoord dat expliciet moest worden ingesteld...

Wat zijn jullie ervaringen en ervaringen met NULL? Hoe zou een betere SQL volgens jou werken?

Lukas Eder is oprichter en CEO van Data Geekery GmbH, gevestigd in Zürich, Zwitserland. Data Geekery verkoopt sinds 2013 databaseproducten en -diensten rond Java en SQL.

Sinds zijn masterstudie aan EPFL in 2006 is hij gefascineerd door de interactie tussen Java en SQL. De meeste van deze ervaring heeft hij opgedaan in het Zwitserse E-Banking veld via verschillende varianten (JDBC, Hibernate, meestal met Oracle). Deze kennis deelt hij graag op verschillende congressen, JUG's, in-house presentaties en zijn bedrijfsblog.


  1. Hoe PERIOD_DIFF() werkt in MariaDB

  2. SQL Server Bulk Insert – Deel 1

  3. 4 manieren om rijen te vinden die hoofdletters bevatten in PostgreSQL

  4. De geparametriseerde query ..... verwacht de parameter '@units', die niet is opgegeven