sql >> Database teknologi >  >> RDS >> Database

50 Shades of NULL – De forskellige betydninger af NULL i SQL

Tony Hoare, der for det meste omtales som opfinderen af ​​NULL-referencen, kalder det nu en milliardfejl, som stort set alle sprog nu "lider" under, inklusive SQL.

Citerer Tony (fra hans Wikipedia-artikel):

Jeg kalder det min milliardfejl. Det var opfindelsen af ​​nulreferencen i 1965. På det tidspunkt var jeg ved at designe det første omfattende typesystem til referencer i et objektorienteret sprog (ALGOL W). Mit mål var at sikre, at al brug af referencer skulle være absolut sikker, med kontrol udført automatisk af compileren. Men jeg kunne ikke modstå fristelsen til at indsætte en nulreference, simpelthen fordi det var så nemt at implementere. Dette har ført til utallige fejl, sårbarheder og systemnedbrud, som sandsynligvis har forårsaget en milliard dollars af smerte og skade i de sidste fyrre år.

Det interessante her er, at Tony blev fristet til at implementere den reference, fordi det var nemt at gøre. Men hvorfor havde han overhovedet brug for sådan en reference?

De forskellige betydninger af NULL

I en perfekt verden ville vi ikke have brug for NULL. Hver person har et fornavn og et efternavn. Hver person har en fødselsdato, et job osv. Eller har de?

Det gør de desværre ikke.

Ikke alle lande bruger begrebet for- og efternavne.

Ikke alle mennesker har et arbejde. Eller nogle gange kender vi ikke deres job. Eller vi er ligeglade.

Det er her NULL er yderst nyttigt. NULL kan modellere alle disse tilstande, som vi egentlig ikke ønsker at modellere. NULL kan være:

  • Værdien "udefineret" , dvs. den værdi, der endnu ikke er defineret (sandsynligvis af tekniske årsager), men som meget vel kan defineres senere. Tænk på en person, som vi vil tilføje til databasen for at kunne bruge den i andre tabeller. På et senere tidspunkt tilføjer vi denne persons job.
  • Værdien "ukendt" , dvs. den værdi, som vi ikke kender (og måske aldrig kender). Måske kan vi ikke længere spørge denne person eller deres slægtninge om deres fødselsdato - oplysningerne vil være for evigt tabt. Men vi vil stadig gerne modellere personen, så vi bruger NULL i betydningen UKENDT (hvilket er dens sande betydning i SQL, som vi vil se senere).
  • Den "valgfri" værdi , dvs. den værdi, der ikke skal defineres. Bemærk, at den "valgfri" værdi også vises i tilfælde af en OUTER JOIN, når den ydre join ikke producerer nogen værdier på den ene side af forholdet. Eller også ved brug af GROUPING SET, hvor forskellige kombinationer af GROUP BY-kolonner kombineres (eller efterlades tomme).
  • Værdien "slettet" eller "undgået" , dvs. den værdi, som vi ikke ønsker at angive. Måske registrerer vi normalt en persons civilstand, som det gøres i nogle jurisdiktioner, men ikke i andre, hvor det ikke er lovligt at registrere nogen personoplysninger af denne type. Derfor ønsker vi ikke at kende denne værdi i nogle tilfælde.
  • Den "særlige" værdi i en given kontekst , dvs. den værdi, som vi ikke på anden måde kan modellere i rækken af ​​mulige værdier. Dette gøres ofte, når man arbejder med datointervaller. Lad os antage, at en persons job er afgrænset af to datoer, og hvis personen i øjeblikket arbejder i denne stilling, bruger vi NULL til at sige, at perioden er ubegrænset i slutningen af ​​datointervallet.
  • Den "tilfældige" NULL , dvs. NULL-værdien, der bare er NULL, fordi udviklerne ikke var opmærksomme. I mangel af en eksplicit NOT NULL-begrænsning, antager de fleste databaser, at kolonner er nullbare. Og når først kolonner er nullable, kan udviklere måske "ved et uheld" sætte NULL-værdier i deres rækker, hvor de ikke engang havde til hensigt at.

Som vi har set ovenfor er disse kun nogle få udvalgte af 50 Shades of NULL .

Følgende eksempel viser forskellige betydninger af NULL i et konkret SQL-eksempel:




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

Folk har altid skændtes om fraværet af en værdi

Når NULL er så nyttig en værdi, hvorfor bliver folk så ved med at kritisere den?

Alle disse tidligere use-cases for NULL (og andre) vises i denne interessante, nylige tale af C.J. Date om "The Problem of Missing Information" (se videoen på YouTube).

Moderne SQL kan gøre en masse fantastiske ting, som få udviklere af generelle sprog som Java, C#, PHP er uvidende om. Jeg viser dig et eksempel længere nede.

På en måde er C.J. Date enig med Tony Hoare i, at det er et meget dårligt valg at (misbruge) NULL til alle disse forskellige typer af "manglende oplysninger".

For eksempel i elektronik anvendes lignende teknikker til at modellere ting som 1, 0, "konflikt", "utildelt", "ukendt", "ligeglad", "høj impedans". Bemærk dog, hvordan forskellige specielle værdier inden for elektronik bruges til disse ting i stedet for en enkelt speciel NULL-værdi . Er dette virkelig bedre? Hvordan har JavaScript-programmører det med at skelne mellem forskellige "falske" værdier, såsom "null", "udefineret", "0", "NaN", den tomme streng ''? Er det virkelig bedre?

Apropos nul:Når vi forlader SQL-pladsen et øjeblik og går ind i matematik, vil vi se, at gamle kulturer som romerne eller grækerne havde de samme problemer med tallet nul. Faktisk havde de ikke engang nogen måde at repræsentere nul i modsætning til andre kulturer, som det kan ses i Wikipedia-artiklen om tallet nul. Citat fra artiklen:

Optegnelser viser, at de gamle grækere virkede usikre på status for nul som et tal. De spurgte sig selv:"Hvordan kan intet være noget?", hvilket førte til filosofiske og, i middelalderen, religiøse argumenter om naturen og eksistensen af ​​nul og vakuum.

Som vi kan se, strækker de "religiøse argumenter" sig klart til datalogi og software, hvor vi stadig ikke med sikkerhed ved, hvad vi skal gøre med fraværet af en værdi.

Tilbage til virkeligheden:NULL i SQL

Mens folk (inklusive akademikere) stadig ikke er enige om, hvorvidt vi har brug for nogen kodning for "udefineret", "ukendt", "valgfrit", "slettet", "særlig", så lad os vende tilbage til virkeligheden og de dårlige dele om SQL's NULL.

En ting, der ofte glemmes, når man beskæftiger sig med SQL’s NULL, er, at den formelt implementerer UNKNOWN casen, som er en speciel værdi, der er en del af såkaldt tre-værdi logik, og det gør det, inkonsekvent, f.eks. i tilfælde af UNION- eller INTERSECT-operationer.

Hvis vi går tilbage til vores model:





Hvis vi f.eks. ønsker at finde alle personer, der ikke er registreret som værende gift, intuitivt, vil vi gerne skrive følgende udsagn:

SELECT * FROM person WHERE marital_status != 'married'

Desværre vil ovenstående forespørgsel ikke returnere de værdier, der ikke har nogen eksplicit ægteskabelig status på grund af logik med tre værdier og SQL's NULL. Derfor bliver vi nødt til at skrive et ekstra eksplicit prædikat:

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

Eller vi tvinger værdien til en NOT NULL-værdi, før vi sammenligner den

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

Logik med tre værdier er svært. Og det er ikke det eneste problem med NULL i SQL. Her er flere ulemper ved at bruge NULL:

  • Der er kun én NULL, når vi virkelig ønskede at kode flere forskellige "fraværende" eller "særlige" værdier. Udvalget af nyttige specialværdier afhænger i høj grad af domænet og de datatyper, der bruges. Alligevel kræves der altid domænekendskab for at fortolke betydningen af ​​en nullbar kolonne korrekt, og forespørgsler skal designes omhyggeligt for at forhindre, at de forkerte resultater returneres, som vi så ovenfor.
  • Igen er logik med tre værdier meget svær at få ret. Selvom ovenstående eksempel stadig er ret simpelt, hvad tror du, at følgende forespørgsel vil give?
    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', NULL)
    

    Nøjagtigt. Det vil ikke give noget som helst, som forklaret i denne artikel her. Kort sagt, ovenstående forespørgsel er den samme som nedenstående:

    SELECT * FROM person 
    WHERE marital_status != 'married'
    AND marital_status != NULL -- This is always NULL / UNKNOWN
    
  • Oracle-databasen behandler NULL og den tomme streng '' som det samme. Dette er meget vanskeligt, da du ikke umiddelbart vil bemærke, hvorfor følgende forespørgsel altid returnerer et tomt resultat:

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

  • Oracle (igen) sætter ikke NULL-værdier i indekser. Dette er kilden til mange grimme ydeevneproblemer, f.eks. når du bruger en nullbar kolonne i et NOT IN-prædikat som sådan:

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

    Med Oracle vil ovenstående anti-join resultere i en fuld tabelscanning, uanset om du har et indeks på some_nullable_column. På grund af logik med tre værdier og fordi Oracle ikke sætter NULL-værdier i indekser, bliver motoren nødt til at ramme bordet og kontrollere hver værdi for at være sikker på, at der ikke er mindst én NULL-værdi i sættet, hvilket ville gøre hele prædikat UKENDT.

Konklusion

Vi har endnu ikke løst NULL-problemet på de fleste sprog og platforme. Selvom jeg påstår, at NULL IKKE er den milliardfejl, som Tony Hoare forsøger at undskylde for, er NULL bestemt heller langt fra perfekt.

Hvis du vil forblive på den sikre side med dit databasedesign, skal du undgå NULL'er for enhver pris, medmindre du absolut har brug for en af ​​disse specielle værdier for at kode med NULL. Husk, disse værdier er:"udefineret", "ukendt", "valgfri", "slettet" og "særlig" og mere:The 50 Shades of NULL . Hvis du ikke er i en sådan situation, skal du som standard altid tilføje en NOT NULL-begrænsning til hver kolonne i din database. Dit design bliver meget renere, og din ydeevne meget bedre.

Hvis kun NOT NULL var standard i DDL, og NULLABLE nøgleordet, der skulle angives eksplicit...

Hvad er dine holdninger og erfaringer med NULL? Hvordan ville en bedre SQL fungere efter din mening?

Lukas Eder er grundlægger og administrerende direktør for Data Geekery GmbH, beliggende i Zürich, Schweiz. Data Geekery har solgt databaseprodukter og -tjenester omkring Java og SQL siden 2013.

Lige siden hans kandidatstudium på EPFL i 2006 har han været fascineret af samspillet mellem Java og SQL. Det meste af denne erfaring har han opnået i det schweiziske E-Banking-område gennem forskellige varianter (JDBC, Hibernate, mest med Oracle). Han deler gerne denne viden på forskellige konferencer, JUG'er, interne præsentationer og hans firmablog.


  1. Hvilke effekter har det at bruge en binær kollation?

  2. UTF-8 hele vejen igennem

  3. Vælg rækkenummer i postgres

  4. Hvad er den mest ligetil måde at udfylde tomme datoer i SQL-resultater (i enten mysql- eller perl-enden)?