Tony Hoare, som mest kallas uppfinnaren av NULL-referensen, kallar det nu ett miljarddollarmisstag som i stort sett alla språk nu "lider" av, inklusive SQL.
Citerar Tony (från hans Wikipedia-artikel):
Jag kallar det mitt miljardmisstag. Det var uppfinningen av nollreferensen 1965. Vid den tiden designade jag det första heltäckande typsystemet för referenser i ett objektorienterat språk (ALGOL W). Mitt mål var att säkerställa att all användning av referenser skulle vara absolut säker, med kontroll utförd automatiskt av kompilatorn. Men jag kunde inte motstå frestelsen att lägga in en nollreferens, helt enkelt för att det var så lätt att implementera. Detta har lett till otaliga fel, sårbarheter och systemkrascher, som förmodligen har orsakat en miljard dollar av smärta och skada under de senaste fyrtio åren.Det intressanta här är att Tony var frestad att implementera den referensen eftersom det var lätt att göra. Men varför behövde han ens en sådan referens?
De olika betydelserna av NULL
I en perfekt värld skulle vi inte behöva NULL. Varje person har ett förnamn och ett efternamn. Varje person har ett födelsedatum, ett jobb, etc. Eller har de?
Tyvärr gör de inte det.
Inte alla länder använder begreppet för- och efternamn.
Alla människor har inte ett jobb. Eller ibland kan vi inte deras jobb. Eller så bryr vi oss inte.
Det är här NULL är extremt användbart. NULL kan modellera alla dessa tillstånd som vi egentligen inte vill modellera. NULL kan vara:
- Det "odefinierade" värdet , det vill säga det värde som ännu inte är definierat (troligen av tekniska skäl) men som mycket väl kan definieras senare. Tänk på en person som vi vill lägga till i databasen för att kunna använda den i andra tabeller. I något senare skede kommer vi att lägga till den personens jobb.
- Det "okända" värdet , det vill säga värdet som vi inte känner till (och kanske aldrig vet). Kanske kan vi inte längre fråga den här personen eller deras släktingar om deras födelsedatum – informationen kommer att gå förlorad för alltid. Men vi vill fortfarande modellera personen, så vi använder NULL i betydelsen OKÄNT (vilket är dess sanna betydelse i SQL, som vi kommer att se senare).
- Det "valfria" värdet , det vill säga värdet som inte behöver definieras. Observera att det "valfria" värdet också visas i fallet med en OUTER JOIN, när den yttre sammanfogningen inte ger några värden på ena sidan av relationen. Eller också när du använder GROUPING SET, där olika kombinationer av GROUP BY-kolumner kombineras (eller lämnas tomma).
- Värdet "borttaget" eller "undvikt" , det vill säga värdet som vi inte vill ange. Kanske registrerar vi vanligtvis en persons civilstånd som görs i vissa jurisdiktioner, men inte i andra, där det inte är lagligt att registrera några personuppgifter av denna typ. Därför vill vi inte veta detta värde i vissa fall.
- Det "speciella" värdet i ett givet sammanhang , det vill säga värdet som vi inte kan modellera på annat sätt inom intervallet av möjliga värden. Detta görs ofta när man arbetar med datumintervall. Låt oss anta att en persons jobb är begränsat av två datum, och om personen för närvarande arbetar i den positionen använder vi NULL för att säga att perioden är obegränsad i slutet av datumintervallet.
- Den "oavsiktliga" NULL , det vill säga NULL-värdet som bara är NULL eftersom utvecklarna inte var uppmärksamma. I avsaknad av en explicit NOT NULL-begränsning antar de flesta databaser att kolumner är nullbara. Och när kolumner är nullbara kan utvecklare "av misstag" lägga NULL-värden i sina rader, där de inte ens hade för avsikt att göra det.
Som vi har sett ovan är dessa bara några få utvalda av 50 Shades of NULL .
Följande exempel visar olika betydelser av NULL i ett konkret SQL-exempel:
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) );
Människor har alltid bråkat om frånvaron av ett värde
När NULL är ett så användbart värde, varför fortsätter folk att kritisera det?
Alla dessa tidigare användningsfall för NULL (och andra) visas i detta intressanta, senaste föredrag av C.J. Date om "The Problem of Missing Information" (se videon på YouTube).
Modern SQL kan göra många fantastiska saker som få utvecklare av allmänna språk som Java, C#, PHP är omedvetna om. Jag ska visa dig ett exempel längre ner.
På sätt och vis håller C.J. Date med Tony Hoare om att (missbruk)användning av NULL för alla dessa olika typer av "saknad information" är ett mycket dåligt val.
Till exempel inom elektronik används liknande tekniker för att modellera saker som 1, 0, "konflikt", "otilldelad", "okänt", "bryr sig inte", "hög impedans". Lägg dock märke till hur olika specialvärden inom elektronik används för dessa saker, snarare än ett enda speciellt NULL-värde . Är detta verkligen bättre? Hur tycker JavaScript-programmerare om skillnaden mellan olika "falska" värden, som "null", "odefinierad", "0", "NaN", den tomma strängen ''? Är det här verkligen bättre?
På tal om noll:När vi lämnar SQL-utrymmet för ett ögonblick och går in i matematik, kommer vi att se att antika kulturer som romarna eller grekerna hade samma problem med siffran noll. Faktum är att de inte ens hade något sätt att representera noll till skillnad från andra kulturer som kan ses i Wikipedia-artikeln om talet noll. Citerar från artikeln:
Uppgifter visar att de gamla grekerna verkade osäkra på statusen noll som ett tal. De frågade sig själva, "Hur kan ingenting vara något?", vilket ledde till filosofiska och, genom medeltiden, religiösa argument om nolls och vakuumets natur och existens.Som vi kan se sträcker sig de "religiösa argumenten" tydligt till datavetenskap och programvara, där vi fortfarande inte säkert vet vad vi ska göra med frånvaron av ett värde.
Tillbaka till verkligheten:NULL i SQL
Medan människor (inklusive akademiker) fortfarande inte är överens om huruvida vi behöver någon kodning för "odefinierad", "okänd", "valfri", "raderad", "speciell", låt oss gå tillbaka till verkligheten och de dåliga delarna om SQL är NULL.
En sak som ofta glöms bort när man hanterar SQL’s NULL är att den formellt implementerar fallet UNKNOWN, vilket är ett specialvärde som är en del av så kallad trevärdig logik, och det gör det, inkonsekvent, t.ex. i fallet med UNION- eller INTERSECT-operationer.
Om vi går tillbaka till vår modell:
Om vi till exempel vill hitta alla personer som inte är registrerade som gifta, intuitivt, skulle vi vilja skriva följande uttalande:
SELECT * FROM person WHERE marital_status != 'married'
Tyvärr, på grund av logik med tre värden och SQLs NULL, kommer ovanstående fråga inte att returnera de värden som inte har någon explicit civilstatus. Därför måste vi skriva ett ytterligare, explicit predikat:
SELECT * FROM person WHERE marital_status != 'married' OR marital_status IS NULL
Eller så tvingar vi värdet till ett INTE NULL-värde innan vi jämför det
SELECT * FROM person WHERE COALESCE(marital_status, 'null') != 'married'
Logik med tre värden är svårt. Och det är inte det enda problemet med NULL i SQL. Här är fler nackdelar med att använda NULL:
- Det finns bara en NULL när vi verkligen ville koda flera olika "frånvarande" eller "speciella" värden. Utbudet av användbara specialvärden beror mycket på domänen och de datatyper som används. Ändå krävs alltid domänkunskap för att korrekt tolka innebörden av en nollbar kolumn, och frågor måste utformas noggrant för att förhindra att fel resultat returneras, som vi såg ovan.
- Återigen, trevärdig logik är mycket svår att få rätt. Även om exemplet ovan fortfarande är ganska enkelt, vad tror du att följande fråga kommer att ge?
SELECT * FROM person WHERE marital_status NOT IN ('married', NULL)
Exakt. Det kommer inte att ge något alls, som förklaras i den här artikeln här. Kort sagt, ovanstående fråga är densamma som nedan:
SELECT * FROM person WHERE marital_status != 'married' AND marital_status != NULL -- This is always NULL / UNKNOWN
-
Oracle-databasen behandlar NULL och den tomma strängen '' som samma sak. Det här är mycket knepigt eftersom du inte direkt kommer att märka varför följande fråga alltid returnerar ett tomt resultat:
SELECT * FROM person WHERE marital_status NOT IN ('married', '')
-
Oracle (igen) lägger inte NULL-värden i index. Detta är källan till många otäcka prestandaproblem, t.ex. när du använder en nollbar kolumn i ett NOT IN-predikat som sådant:
SELECT * FROM person WHERE marital_status NOT IN ( SELECT some_nullable_column FROM some_table )
Med Oracle kommer ovanstående anti-join att resultera i en fullständig tabellsökning, oavsett om du har ett index på some_nullable_column. På grund av logik med tre värden och eftersom Oracle inte sätter NULL i index, kommer motorn att behöva träffa tabellen och kontrollera varje värde bara för att vara säker på att det inte finns minst ett NULL-värde i uppsättningen, vilket skulle göra att hela predikatet OKÄNT.
Slutsats
Vi har inte löst NULL-problemet ännu på de flesta språk och plattformar. Även om jag hävdar att NULL INTE är det miljarddollarmisstag som Tony Hoare försöker be om ursäkt för, så är NULL verkligen långt ifrån perfekt heller.
Om du vill hålla dig på den säkra sidan med din databasdesign, undvik NULL till varje pris, såvida du inte absolut behöver något av dessa specialvärden för att koda med NULL. Kom ihåg att dessa värden är:"odefinierad", "okänd", "valfritt", "raderad" och "speciell" med mera:The 50 Shades of NULL . Om du inte är i en sådan situation, lägger du alltid till en NOT NULL-begränsning till varje kolumn i din databas. Din design blir mycket renare och din prestanda mycket bättre.
Om bara NOT NULL var standard i DDL, och NULLABLE nyckelordet som behövde ställas in explicit...
Vad är dina åsikter och erfarenheter av NULL? Hur skulle en bättre SQL fungera enligt dig?
Lukas Eder är grundare och VD för Data Geekery GmbH, beläget i Zürich, Schweiz. Data Geekery har sålt databasprodukter och tjänster kring Java och SQL sedan 2013.
Ända sedan hans masterstudier vid EPFL 2006 har han varit fascinerad av samspelet mellan Java och SQL. Det mesta av denna erfarenhet har han fått inom det schweiziska e-bankingområdet genom olika varianter (JDBC, Hibernate, mestadels med Oracle). Han delar gärna med sig av denna kunskap på olika konferenser, JUGs, interna presentationer och sin företagsblogg.