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

50 sfumature di NULL - I diversi significati di NULL in SQL

Tony Hoare, a cui ci si riferisce principalmente come l'inventore del riferimento NULL, ora lo definisce un errore da un miliardo di dollari di cui quasi tutti i linguaggi stanno "soffrendo", incluso SQL.

Citando Tony (dal suo articolo su Wikipedia):

Lo chiamo il mio errore da un miliardo di dollari. È stata l'invenzione del riferimento nullo nel 1965. A quel tempo stavo progettando il primo sistema di tipi completo per i riferimenti in un linguaggio orientato agli oggetti (ALGOL W). Il mio obiettivo era garantire che tutto l'uso dei riferimenti fosse assolutamente sicuro, con il controllo eseguito automaticamente dal compilatore. Ma non ho resistito alla tentazione di inserire un riferimento nullo, semplicemente perché era così facile da implementare. Ciò ha portato a innumerevoli errori, vulnerabilità e arresti anomali del sistema, che hanno probabilmente causato un miliardo di dollari di dolore e danni negli ultimi quarant'anni.

La cosa interessante qui è che Tony è stato tentato di implementare quel riferimento perché era facile da fare. Ma perché aveva bisogno di un simile riferimento?

I diversi significati di NULL

In un mondo perfetto, non avremmo bisogno di NULL. Ogni persona ha un nome e un cognome. Ogni persona ha una data di nascita, un lavoro, ecc. O no?

Sfortunatamente, non lo fanno.

Non tutti i paesi utilizzano il concetto di nome e cognome.

Non tutte le persone hanno un lavoro. O a volte, non conosciamo il loro lavoro. Oppure non ci interessa.

È qui che NULL è estremamente utile. NULL può modellare tutti questi stati che in realtà non vogliamo modellare. NULL può essere:

  • Il valore "non definito" , ovvero il valore che non è ancora definito (probabilmente per ragioni tecniche) ma potrebbe essere definito in seguito. Pensa a una persona che vogliamo aggiungere al database per usarlo in altre tabelle. In una fase successiva, aggiungeremo il lavoro di quella persona.
  • Il valore "sconosciuto" , ovvero il valore che non conosciamo (e forse non conosceremo mai). Forse non possiamo più chiedere a questa persona o ai suoi parenti la loro data di nascita:le informazioni andranno perse per sempre. Ma vogliamo ancora modellare la persona, quindi usiamo NULL nel senso di UNKNOWN (che è il suo vero significato in SQL, come vedremo più avanti).
  • Il valore "facoltativo" , ovvero il valore che non deve essere definito. Si noti che il valore "opzionale" appare anche nel caso di OUTER JOIN, quando l'outer join non produce alcun valore su un lato della relazione. O anche quando si utilizzano GROUPING SETS, dove diverse combinazioni di colonne GROUP BY vengono combinate (o lasciate vuote).
  • Il valore "eliminato" o "evitato" , ovvero il valore che non vogliamo specificare. Forse di solito registriamo lo stato civile di una persona come avviene in alcune giurisdizioni, ma non in altre, dove non è legale registrare dati personali di questo tipo. Pertanto, in alcuni casi non vogliamo conoscere questo valore.
  • Il valore "speciale" in un determinato contesto , ovvero il valore che non possiamo modellare altrimenti nell'intervallo dei valori possibili. Questo viene spesso fatto quando si lavora con intervalli di date. Supponiamo che il lavoro di una persona sia limitato da due date e, se la persona sta attualmente lavorando in quella posizione, useremo NULL per dire che il periodo è illimitato alla fine dell'intervallo di date.
  • Il NULL "accidentale" , ovvero il valore NULL che è solo NULL perché gli sviluppatori non hanno prestato attenzione. In assenza di un vincolo NOT NULL esplicito, la maggior parte dei database presuppone che le colonne siano annullabili. E una volta che le colonne sono annullabili, gli sviluppatori potrebbero semplicemente inserire "accidentalmente" valori NULL nelle loro righe, dove non avevano nemmeno intenzione di farlo.

Come abbiamo visto sopra, queste sono solo alcune delle 50 sfumature di NULL .

L'esempio seguente mostra diversi significati di NULL in un esempio SQL concreto:




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

Le persone hanno sempre discusso dell'assenza di un valore

Quando NULL è un valore così utile, perché le persone continuano a criticarlo?

Tutti questi casi d'uso precedenti per NULL (e altri) sono mostrati in questo interessante e recente intervento di CJ Date su "The Problem of Missing Information" (guarda il video su YouTube).

L'SQL moderno può fare molte cose fantastiche di cui pochi sviluppatori di linguaggi generici come Java, C#, PHP non sono a conoscenza. Ti mostro un esempio più in basso.

In un certo senso, CJ Date concorda con Tony Hoare sul fatto che (ab)usare NULL per tutti questi diversi tipi di "informazioni mancanti" è una pessima scelta.

Ad esempio, in elettronica, tecniche simili vengono applicate per modellare cose come 1, 0, "conflitto", "non assegnato", "sconosciuto", "non importa", "alta impedenza". Nota però come nell'elettronica valori speciali differenti sono usati per queste cose, piuttosto che un singolo valore NULL speciale . Questo è davvero meglio? Come si sentono i programmatori JavaScript riguardo alla distinzione tra diversi valori "falsi", come "null", "undefined", "0", "NaN", la stringa vuota ''? È davvero meglio?

A proposito di zero:quando lasciamo per un momento lo spazio SQL e andiamo in matematica, vedremo che culture antiche come i romani o i greci hanno avuto gli stessi problemi con il numero zero. In effetti, non avevano nemmeno modo di rappresentare lo zero a differenza di altre culture, come si può vedere nell'articolo di Wikipedia sul numero zero. Citando l'articolo:

I registri mostrano che gli antichi greci sembravano insicuri sullo stato di zero come numero. Si sono chiesti:"Come può niente essere qualcosa?", portando ad argomenti filosofici e, nel periodo medievale, religiosi sulla natura e l'esistenza dello zero e del vuoto.

Come si vede, le “argomentazioni religiose” si estendono chiaramente all'informatica e al software, dove ancora non sappiamo con certezza cosa fare con l'assenza di un valore.

Ritorno alla realtà:NULL in SQL

Mentre le persone (inclusi gli accademici) non sono ancora d'accordo sul fatto che abbiamo bisogno di una codifica per "non definito", "sconosciuto", "opzionale", "cancellato", "speciale", torniamo alla realtà e alle parti negative di SQL è NULL.

Una cosa che viene spesso dimenticata quando si ha a che fare con NULL di SQL è che implementa formalmente il caso UNKNOWN, che è un valore speciale che fa parte della cosiddetta logica a tre valori, e lo fa, in modo incoerente, ad es. nel caso di operazioni UNION o INTERSECT.

Se torniamo al nostro modello:





Se, ad esempio, vogliamo trovare tutte le persone che non sono registrate come sposate, intuitivamente vorremmo scrivere la seguente dichiarazione:

SELECT * FROM person WHERE marital_status != 'married'

Sfortunatamente, a causa della logica a tre valori e del valore NULL di SQL, la query precedente non restituirà quei valori che non hanno alcuno stato civile esplicito. Quindi, dovremo scrivere un predicato esplicito aggiuntivo:

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

Oppure, impostiamo il valore a un valore NOT NULL prima di confrontarlo

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

La logica a tre valori è difficile. E non è l'unico problema con NULL in SQL. Ecco altri svantaggi dell'utilizzo di NULL:

  • C'è solo un NULL, quando volevamo davvero codificare diversi valori "assenti" o "speciali". L'intervallo di valori speciali utili dipende fortemente dal dominio e dai tipi di dati utilizzati. Tuttavia, la conoscenza del dominio è sempre richiesta per interpretare correttamente il significato di una colonna nullable e le query devono essere progettate con attenzione per evitare che vengano restituiti risultati errati, come abbiamo visto sopra.
  • Ancora una volta, la logica a tre valori è molto difficile da correggere. Sebbene l'esempio sopra sia ancora piuttosto semplice, cosa pensi produrrà la seguente query?
    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', NULL)
    

    Esattamente. Non produrrà nulla, come spiegato in questo articolo qui. In breve, la query sopra è la stessa di quella seguente:

    SELECT * FROM person 
    WHERE marital_status != 'married'
    AND marital_status != NULL -- This is always NULL / UNKNOWN
    
  • Il database Oracle considera NULL e la stringa vuota '' come la stessa cosa. Questo è molto complicato in quanto non noterai immediatamente perché la seguente query restituisce sempre un risultato vuoto:

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

  • Oracle (di nuovo) non inserisce valori NULL negli indici. Questa è la fonte di molti brutti problemi di prestazioni, ad esempio, quando si utilizza una colonna nullable in un predicato NOT IN in quanto tale:

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

    Con Oracle, l'anti-join di cui sopra risulterà in una scansione completa della tabella, indipendentemente dal fatto che tu abbia un indice su some_nullable_column. A causa della logica a tre valori e poiché Oracle non inserisce NULL negli indici, il motore dovrà raggiungere la tabella e controllare ogni valore solo per essere sicuro che non ci sia almeno un valore NULL nel set, il che renderebbe il intero predicato SCONOSCIUTO.

Conclusione

Non abbiamo ancora risolto il problema NULL nella maggior parte delle lingue e delle piattaforme. Anche se affermo che NULL NON è l'errore da un miliardo di dollari per il quale Tony Hoare cerca di scusarsi, NULL è certamente tutt'altro che perfetto.

Se vuoi stare al sicuro con la progettazione del tuo database, evita i NULL a tutti i costi, a meno che tu non abbia assolutamente bisogno di uno di quei valori speciali per codificare usando NULL. Ricorda, questi valori sono:"non definito", "sconosciuto", "opzionale", "eliminato" e "speciale" e altro ancora:Le 50 sfumature di NULL . Se non ti trovi in ​​una situazione del genere, per impostazione predefinita aggiungi sempre un vincolo NOT NULL a ogni colonna del tuo database. Il tuo design sarà molto più pulito e le tue prestazioni molto migliori.

Se solo NOT NULL fosse il valore predefinito in DDL e NULLABLE la parola chiave che doveva essere impostata in modo esplicito...

Quali sono le tue idee e le tue esperienze con NULL? Come funzionerebbe un SQL migliore secondo te?

Lukas Eder è fondatore e CEO di Data Geekery GmbH, con sede a Zurigo, Svizzera. Data Geekery vende prodotti e servizi di database su Java e SQL dal 2013.

Sin dai suoi studi magistrali all'EPFL nel 2006, è stato affascinato dall'interazione di Java e SQL. Gran parte di questa esperienza l'ha maturata nel campo dell'E-Banking svizzero attraverso varie varianti (JDBC, Hibernate, principalmente con Oracle). È felice di condividere questa conoscenza in varie conferenze, JUG, presentazioni interne e nel suo blog aziendale.