Perché non dovremmo consentire i NULL?


125

Ricordo di aver letto questo articolo sulla progettazione di database e ricordo anche che dicevo che dovresti avere proprietà sul campo di NOT NULL. Non ricordo perché fosse così.

Tutto ciò a cui riesco a pensare è che, come sviluppatore di applicazioni, non dovresti testare NULL e un possibile valore di dati inesistente (ad esempio, una stringa vuota per le stringhe).

Ma cosa fai in caso di date, data e ora (SQL Server 2008)? Dovresti usare una data storica o dal basso.

Qualche idea su questo?


4
Questa risposta ha un'idea dell'utilizzo di NULL dba.stackexchange.com/questions/5176/…
Derek Downey,

10
Veramente? Perché RDBMS ci consente di utilizzare NULL, se non dovremmo usarli? Non c'è niente di sbagliato in NULL fintanto che sai come gestirli.
Fr0zenFir

3
Si trattava di un modello di dati BI? In genere non dovresti consentire le tabelle di null in effetti ... altrimenti, i null sono amici se usati correttamente. =)
sam yi,

2
@ Fr0zenFyr, solo perché un RDBMS ci consente di fare qualcosa non è necessariamente una buona idea farlo. Nulla ci costringe a dichiarare una chiave primaria o una chiave univoca in una tabella, ma con poche eccezioni lo facciamo comunque.
Lennart,

3
Penso che un trattamento completo di questo argomento dovrebbe fare riferimento al requisito originale di Codd secondo cui un RDBMS deve avere un modo sistematico di trattare i dati mancanti. Nel mondo reale, ci sono situazioni in cui viene creata una posizione per i dati, ma non ci sono dati da inserire. Data Architect deve fornire una risposta a questo, sia che si tratti di progettazione di database, programmazione di applicazioni o entrambi. SQL NULL è meno che perfetto per soddisfare questo requisito, ma è meglio di niente.
Walter Mitty,

Risposte:


230

Penso che la domanda sia mal formulata, poiché la formulazione implica che hai già deciso che i NULL sono cattivi. Forse intendevi "Dovremmo consentire i NULL?"

Comunque, ecco la mia opinione: penso che i NULL siano una buona cosa. Quando inizi a prevenire i NULL solo perché "NULL è errato" o "NULL è difficile", inizi a creare dati. Ad esempio, cosa succede se non conosci la mia data di nascita? Che cosa hai intenzione di mettere nella colonna fino a quando non lo sai? Se assomigli a un sacco di gente anti-NULL, entrerai nel 1900-01-01. Ora sarò messo nel reparto geriatrico e probabilmente riceverò una chiamata dalla mia stazione di notizie locale congratulandomi per la mia lunga vita, chiedendomi i miei segreti per vivere una vita così lunga, ecc.

Se è possibile inserire una riga in cui è possibile che tu non conosca il valore di una colonna, penso che NULL abbia molto più senso che scegliere un valore di token arbitrario per rappresentare il fatto che è sconosciuto - un valore che altri devi già sapere, decodificare o chiedere in giro per capire cosa significhi.

Tuttavia, esiste un equilibrio: non tutte le colonne del modello di dati devono essere nullable. In un modulo sono spesso presenti campi opzionali o informazioni che altrimenti non vengono raccolte al momento della creazione della riga. Ma ciò non significa che puoi posticipare il popolamento di tutti i dati. :-)

Inoltre, la possibilità di utilizzare NULL può essere limitata da requisiti cruciali nella vita reale. In campo medico, ad esempio, può essere una questione di vita o di morte sapere perché un valore è sconosciuto. La frequenza cardiaca è NULL perché non c'era un impulso o perché non l'abbiamo ancora misurata? In tal caso, possiamo mettere NULL nella colonna della frequenza cardiaca e avere note o una colonna diversa con un motivo NULL, perché?

Non abbiate paura dei NULL, ma siate disposti a imparare o dettare quando e dove dovrebbero essere usati, e quando e dove non dovrebbero.


3
"un valore di token arbitrario per rappresentare il fatto che è sconosciuto" questo è noto come un valore di sentinella
Alexander

4
Ma cosa ti impedisce di creare una tabella separata in birth_datecui archiviare le date di nascita? Se la data di nascita è sconosciuta, non inserire la data di nascita in birth_date. I nullità sono un disastro.
Eldar Agalarov,

6
@EldarAgalarov Sembra un ragionamento di Trump ("disastro" perché? Come? Per chi? La tua opinione che qualcosa sia un "disastro" non lo rende così). Comunque la data di nascita è solo un esempio. Se hai personale o membri o clienti che hanno 15 colonne potenzialmente nullable, creerai 15 tabelle secondarie? E se ne avessi 50? Cosa succede se la tabella dei fatti DW ha 500? La manutenzione per mantenere grandi NULL spaventosi dal tuo database diventa 10 volte più grave di qualsiasi "disastro" di cui hai paura ...
Aaron Bertrand

3
@AaronBertrand se la tua tabella ha 15 colonne potenzialmente nullable, ha un cattivo odore ^^ Non che un numero enorme di colonne sia intrinsecamente cattivo, ma potrebbe indicare un design errato O richiedere la denormalizzazione. Ma solleverà domande.
programmi

2
@Wildcard Quindi non hai mai visto persone archiviare 1900-01-01per evitare di avere un valore NULL data / ora? Va bene allora. Inoltre, NULL = sconosciuto e sconosciuto = falso. Non sono sicuro di quali problemi questo potrebbe causare se non che le persone non sono nate sapendolo (come se non fossero nate conoscendo molte cose inerenti a un RDBMS complesso). Ancora una volta, agitando le mani e dicendo "Problema! Disastro!" non lo rende così.
Aaron Bertrand

57

I motivi accertati sono:

  • NULL non è un valore e pertanto non ha un tipo di dati intrinseco. I null necessitano di una gestione speciale in tutto il luogo quando anche il codice che si basa su tipi effettivi potrebbe ricevere il NULL non tipizzato.

  • NULL rompe la logica a due valori (familiare True o False) e richiede una logica a tre valori. Questo è molto più complesso da implementare correttamente, ed è certamente poco compreso dalla maggior parte dei DBA e praticamente da tutti i non DBA. Di conseguenza, invita positivamente molti bug sottili nell'applicazione.

  • Il significato semantico di qualsiasi NULL specifico viene lasciato all'applicazione , a differenza dei valori effettivi.

    Semantici come "non applicabile" e "sconosciuto" e "sentinella" sono comuni, e ce ne sono anche altri. Sono frequentemente utilizzati contemporaneamente nello stesso database, anche all'interno della stessa relazione; e sono ovviamente significati inesplicabili, indistinguibili e incompatibili .

  • Non sono necessari per i database relazionali , come affermato in "Come gestire le informazioni mancanti senza valori null" . Un'ulteriore normalizzazione è un ovvio primo passo per provare a liberare una tabella di NULL.

Questo non significa che NULL non dovrebbe mai essere permesso. Essa non sostengono che ci sono molte buone ragioni per non consentire NULL laddove possibile.

Significativamente, sostiene di aver tentato molto - attraverso una migliore progettazione dello schema, migliori motori di database e persino migliori linguaggi di database - per rendere possibile evitare NULL più spesso.

Fabian Pascal risponde a una serie di argomenti, in "Nulls Nullified" .


3
Il tuo link a "Come gestire le informazioni mancanti senza null" mostra abbastanza bene perché non possiamo fare a meno di null: molti dei suggerimenti sarebbero impossibili da implementare in modo razionale sui principali RDBMS così come sono attualmente.
Jack Douglas

7
Jack: Giusto, ma "le attuali implementazioni non possono farlo" non è un argomento per lo status quo :-)
bignose

17
È un po 'come dire che non dovremmo volare perché gli aerei non sono perfetti?
Aaron Bertrand

11
No, sta dicendo che i venditori dovrebbero smettere di invocare scuse per null che potrebbero essere state valide quaranta anni fa, ma sono sopravvissuti a lungo al loro ragionevole periodo di conservazione. I tempi di I / O non sono più nell'ordine di grandezza di 80 ms. I singoli cicli della CPU non sono più nell'ordine di grandezza dei microsecondi. I limiti di memoria non sono più nell'ordine di grandezza di alcuni Meg. A differenza di quarant'anni fa, le velocità e le capacità hardware necessarie per lavorare senza valori NESSUNO esistono attualmente con costi non proibitivi. Sta dicendo che è ora di andare avanti.
Erwin Smout,

2
Il link "NULL confusion" è morto.
jpmc26,

32

Non sono d'accordo, i null sono un elemento essenziale nella progettazione del database. L'alternativa, come hai anche accennato, sarebbe una proliferazione di valori noti per rappresentare il mancante o l'ignoto. Il problema risiede nel fatto che nulla è così ampiamente frainteso e di conseguenza usato in modo inappropriato.

IIRC, Codd ha suggerito che l'attuale implementazione di null (che significa non presente / mancante) potrebbe essere migliorata con due marker null anziché uno, "non presente ma applicabile" e "non presente e non applicabile". Non riesco a immaginare come i progetti relazionali potrebbero essere migliorati da questo personalmente.


2
Suggerisco di avere un set definito dall'utente di diversi tipi di null, e una logica multi-stimata definita dall'utente da accompagnare: p
Jack Douglas

13
Quelle non sono le uniche opzioni. Si esclude l'alternativa di normalizzazione: invece di colonne che possono avere o meno un valore, utilizzare un'altra tabella che può avere o meno una riga corrispondente per la prima tabella. Il significato della presenza o dell'assenza di una riga è implicato nel significato delle tabelle e non esiste un involucro speciale di valori NULL o sentinella ecc.
bignose

7
La presenza di NULL non richiede valori di casing speciali o sentinella. Questi sono solo sintomi di come alcune persone decidono di affrontare i NULL.
Aaron Bertrand

Vale la pena notare che '' è distinto da null su PostgreSQL (anche se non Oracle) e quindi ti dà un marcatore duplice, e potresti usare 0 per colonne numeriche. Il problema con 0 è che non funziona con le chiavi esterne.
Chris Travers,

13

Vorrei iniziare dicendo che non sono un DBA, sono uno sviluppatore a memoria e mantengo e aggiorno i nostri database in base alle nostre esigenze. Detto questo, ho avuto la stessa domanda per alcuni motivi.

  1. Valori nulli rendono lo sviluppo più difficile e soggetto a bug.
  2. I valori nulli rendono le query, le stored procedure e le visualizzazioni più complesse e soggette a bug.
  3. I valori null occupano spazio (? Byte in base alla lunghezza della colonna fissa o 2 byte per la lunghezza della colonna variabile).
  4. Valori nulli possono e spesso influenzano l'indicizzazione e la matematica.

Passo molto tempo a vagliare il carico di risposte, commenti, articoli e consigli su Internet. Inutile dire che la maggior parte delle informazioni erano circa le stesse della risposta di @ AaronBertrand. Ecco perché ho sentito il bisogno di rispondere a questa domanda.

In primo luogo voglio impostare qualcosa di chiaro per tutti i lettori futuri ... I valori NULL rappresentano dati sconosciuti NON dati inutilizzati. Se si dispone di una tabella dei dipendenti con un campo data di fine. Un valore nullo nella data di scadenza è perché è un campo obbligatorio futuro che è attualmente sconosciuto. Ogni dipendente, sia esso attivo o licenziato, ad un certo punto avrà una data aggiunta a quel campo. Questa è secondo me l'unica e unica ragione per un campo Nullable.

Detto questo, la stessa tabella dei dipendenti conterrebbe molto probabilmente un tipo di dati di autenticazione. È comune in un ambiente aziendale che i dipendenti siano elencati nel database per risorse umane e contabilità, ma non sempre hanno o necessitano di dettagli di autenticazione. La maggior parte delle risposte ti indurrebbe a pensare che sia ok annullare quei campi o in alcuni casi creare un account per loro, ma non inviare loro mai le credenziali. Il primo indurrà il tuo team di sviluppo a scrivere codice per verificare la presenza di NULL e gestirli di conseguenza e il secondo comporta un enorme rischio per la sicurezza! Gli account che non sono ancora stati utilizzati nel sistema aumentano solo il numero di possibili punti di accesso per un hacker, inoltre occupano prezioso spazio nel database per qualcosa che non viene mai utilizzato.

Date le informazioni di cui sopra, il modo migliore per gestire i dati nullable che verranno utilizzati è consentire valori nullable. È triste ma vero e i tuoi sviluppatori ti odieranno per questo. Il secondo tipo di dati nullable dovrebbe essere inserito in una tabella correlata (IE: Account, Credenziali, ecc.) E avere una relazione One-to-One. Ciò consente a un utente di esistere senza credenziali a meno che non siano necessarie. Ciò rimuove il rischio aggiuntivo per la sicurezza, lo spazio prezioso del database e fornisce un database molto più pulito.

Di seguito è riportata una struttura della tabella molto semplicistica che mostra sia la colonna nullable richiesta sia una relazione One-to-One.

Nullable sconosciuta e relazione uno a uno

So di essere un po 'in ritardo alla festa da quando questa domanda è stata posta anni fa, ma spero che ciò contribuirà a far luce su questo problema e sul modo migliore di affrontarlo.


2
Vorrei solo cambiarlo in modo che non ci sia TerminationDatenei registri dei dipendenti, ma avere una tabella per la TerminatedEmployeequale i dipendenti vengono spostati (non copiati) dall'applicazione quando vengono chiusi. Ovviamente questo funziona bene con la tabella Account perché non ci saranno account collegati sulla TerminatedEmployeetabella. Se hai ancora bisogno dei numeri di telefono, invertirei le chiavi esterne in modo che i tavoli dei dipendenti e dei dipendenti terminati abbiano l'ID del numero di telefono anziché viceversa.
Programster,

2
Potrei letteralmente andare avanti per giorni sul perché questo sarebbe male. Tabelle ridondanti, pratiche SQL errate, che rendono gli sviluppatori dovrebbero cercare in due punti i dati dei dipendenti, problemi con i report, problemi con URI diretti a un dipendente che non esiste (è stato spostato) e l'elenco continua e via. Va benissimo avere NULL per i campi che un giorno avranno un valore, è un'altra storia avere campi che non si riempiono mai e non hanno mai un uso. Una serie di potenziali problemi e soluzioni alternative per rendere questo lavoro non varrebbe il piccolo problema di controllare NULL su un campo.
Nicholas Aguirre,

1
Non sono d'accordo. L'unica cosa ridondante è quel campo null per la data di scadenza che potrebbe non essere mai riempito. Gli sviluppatori devono solo cercare nella tabella appropriata i dati che desiderano e potrebbero migliorare le prestazioni. Se per qualche motivo, desideri dipendenti con o senza licenziamento, questo viene risolto da un join, ma il 90% delle volte l'applicazione richiederà probabilmente l'uno o l'altro. Penso che il layout che ho specificato sia migliore perché sarebbe impossibile avere una data di scadenza per un dipendente e per lui avere ancora un account.
Programmatore

2
Non ho detto dati ridondanti, ho detto tabelle ridondanti. Inoltre, qualsiasi modifica alle tabelle dei dipendenti deve passare alle tabelle terminate; questo rende l'app soggetta a errori e rende il lavoro dello sviluppatore molto più difficile. Inoltre, un campo Data di fine verrà riempito per quasi tutti. È inutile e problematico creare una seconda struttura di tabella identica e spostare anche i dati. Non includere il test ogni volta per assicurarsi che i dati della tabella siano stati spostati e puliti. È buona norma rimuovere i dati da una tabella, anche se solo per spostarli. Se sei così preoccupato per un singolo campo che ...
Nicholas Aguirre,

1
... che sarà quasi sempre riempito in tempo, quindi fare una tabella terminata con una relazione 1to1 al dipendente. Lavoro con una varietà di database tutto il giorno sia come DBA che come sviluppatore e sono contento di dover ancora trovarne uno con la struttura che hai proposto. Soprattutto dal punto di vista di uno sviluppatore, sarebbe un incubo scrivere e controllare ogni cosa perché non sapresti da quale tabella provenga. Anche scrivendo un join, i dati restituiti al software avrebbero un campo con dati null che richiederebbe comunque di testarlo.
Nicholas Aguirre,

13

A parte tutti i problemi con gli sviluppatori confusi NULL, i NULL hanno un altro inconveniente molto serio: le prestazioni

Le colonne NULL'able sono un disastro dal punto di vista delle prestazioni. Considera l'aritmetica dei numeri interi come esempio. In un mondo sano senza NULL, è "facile" vettorializzare l'aritmetica di numeri interi nel codice del motore di database utilizzando le istruzioni SIMD per eseguire praticamente qualsiasi calcolo a velocità superiori a 1 riga per ciclo CPU. Tuttavia, nel momento in cui si introduce NULL, è necessario gestire tutti i casi speciali creati da NULL. I moderni set di istruzioni della CPU (leggi: x86 / x64 / ARM e anche la logica GPU) semplicemente non sono attrezzati per farlo in modo efficiente.

Considera la divisione come esempio. A un livello molto alto, questa è la logica necessaria con un numero intero non nullo:

if (b == 0)
  do something when dividing by error
else
  return a / b

Con NULL, questo diventa un po 'più complicato. Insieme a bte avrai bisogno di un indicatore se bè nullo e allo stesso modo per a. Il controllo ora diventa:

if (b_null_bit == NULL)
   return NULL
else if (b == 0) 
   do something when dividing by error
else if (a_null_bit == NULL)
   return NULL
else 
   return a / b

L'aritmetica NULL è significativamente più lenta nell'esecuzione su una CPU moderna rispetto all'aritmetica non nulla (di un fattore di circa 2-3x).

Peggiora quando si introduce SIMD. Con SIMD, una moderna CPU Intel può eseguire 4 divisioni intere a 32 bit in una singola istruzione, in questo modo:

x_vector = a_vector / b_vector
if (fetestexception(FE_DIVBYZERO))
   do something when dividing by zero
return x_vector;

Ora, ci sono modi per gestire NULL anche in SIMD land, ma ciò richiede l'uso di più vettori e registri CPU e un po 'di mascheramento intelligente dei bit. Anche con buoni trucchi, la penalità prestazionale dell'aritmetica intera NULL si insinua nell'intervallo 5-10x più lento per espressioni anche relativamente semplici.

Qualcosa di simile a quanto sopra vale per gli aggregati e, in una certa misura, anche per i join.

In altre parole: l'esistenza di NULL in SQL è una discrepanza di impedenza tra la teoria del database e la progettazione effettiva dei computer moderni. C'è una buona ragione per cui NULL confonde gli sviluppatori - poiché un numero intero non può essere NULL nella maggior parte dei linguaggi di programmazione sani - non è proprio così che funzionano i computer.


10

Domande interessanti.

Tutto ciò a cui riesco a pensare è che, come sviluppatore di applicazioni, non dovresti testare NULL e un possibile valore di dati inesistente (ad esempio, una stringa vuota per le stringhe).

È più complicato di così. Null ha un numero di significati distinti e una ragione davvero importante per non consentire valori nulli in molte colonne è che quando la colonna è nulla, ciò significa una sola cosa (ovvero che non è stata mostrata in un join esterno). Inoltre, consente di impostare standard minimi di immissione dei dati, il che è davvero utile.

Ma cosa fai in caso di date, data e ora (SQL Server 2008)? Dovresti usare una data storica o dal basso.

Ciò illustra immediatamente un problema con i null, vale a dire che un valore memorizzato in una tabella può significare "questo valore non si applica" o "non lo sappiamo". Con le stringhe, una stringa vuota può fungere da "questo non si applica" ma con date e orari non esiste una convenzione del genere perché non esiste un valore valido che significa convenzionalmente questo. In genere lì rimarrai bloccato usando i NULL.

Ci sono modi per aggirare questo problema (aggiungendo più relazioni e unendo) ma quelli pongono esattamente gli stessi problemi di chiarezza semantica che hanno i NULL nel database. Per questi database non me ne preoccuperei. Non c'è proprio niente che tu possa fare al riguardo.

EDIT: un'area in cui i NULL sono indispensabili è nelle chiavi esterne. Qui in genere hanno un solo significato, identico al null nel significato del join esterno. Questa è ovviamente un'eccezione al problema.


10

L'articolo di Wikipedia su SQL Null contiene alcune interessanti osservazioni sul valore NULL e, come risposta agnostica al database, fintanto che si è consapevoli dei potenziali effetti di avere valori NULL per il proprio RDBMS specifico, sono accettabili nella progettazione. Se non lo fossero, non saresti in grado di specificare le colonne come nullable.

Basta essere consapevoli di come RDBMS li gestisce nelle operazioni SELECT come la matematica e anche negli indici.


-12

Wow, la risposta corretta "Non consentire NULL quando non è necessario perché degradano le prestazioni" è in qualche modo l'ultima risposta valutata. Lo voterò ed elaborerò. Quando un RDBMS consente NULL per una colonna non sparsa, quella colonna viene aggiunta a una bitmap che tiene traccia del valore NULL per ogni singola riga. Quindi aggiungendo l'abilità NULL a una colonna in una tabella in cui tutte le colonne non consentono NULL, si aumenta lo spazio di archiviazione richiesto per salvare la tabella. Inoltre, è necessario che RDBMS legga e scriva sulla bitmap, compromettendo le prestazioni su tutte le operazioni.

Inoltre, in diversi casi, consentire NULL interromperà 3NF. Mentre non sono un pignolo per 3NF come molti dei miei colleghi, considera il seguente scenario:

Nella tabella Person c'è una colonna, chiamata DateOfDeath, che è nullable. Se una persona è morta, verrà compilata con la relativa DateOfDeath, altrimenti verrà lasciata NULL. Esiste anche una colonna di bit non annullabile denominata IsAlive. Questa colonna è impostata su 1 se la persona è viva e su 0 se la persona è morta. La stragrande maggioranza delle procedure memorizzate utilizza la colonna IsAlive, a loro importa solo se una persona è viva, non il loro DateOfDeath.

Tuttavia, la colonna IsAlive interrompe la normalizzazione del database, poiché è completamente derivabile da DateOfDeath. Ma poiché IsAlive è cablato nella maggior parte degli SP, la soluzione semplice è rendere DateOfDeath non nullable e assegnare un valore predefinito alla colonna nel caso in cui la persona sia ancora viva. I pochi SP che utilizzano DateOfDeath possono quindi essere riscritti per controllare la colonna IsAlive e onorare DateOfDeath solo se la persona non è viva. Ancora una volta, poiché la maggior parte degli SP si preoccupa solo di IsAlive (un po ') e non di DateOfDeath (una data) che utilizza questo schema accelera notevolmente l'accesso.

Un utile script T-SQL per trovare colonne nullable senza NULL in tutti gli schemi è:

select 'IF NOT EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' WHERE ' + QUOTENAME(c.name) + ' IS NULL)
    AND (SELECT COUNT(*) FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ') > 1 PRINT ''' + s.name + '.' + t.name + '.' + REPLACE(c.name, '''', '''''') + ''''
    from sys.columns c
    inner join sys.tables t ON c.object_id = t.object_id
    inner join sys.schemas s ON s.schema_id = t.schema_id
    where c.is_nullable = 1 AND c.is_computed = 0
    order by s.name, t.name, c.name;

Se lo esegui su una copia del database di produzione, puoi trovare gli sviluppatori di colonne contrassegnati come che consentono NULL che non hanno NULL in pratica. La stragrande maggioranza di questi può essere contrassegnata come NOT NULL, aumentando così le prestazioni e riducendo lo spazio di archiviazione.

Potrebbe non essere possibile eliminare tutti i NULL in tutte le tabelle e avere comunque un design pulito, ma esiste un notevole vantaggio nell'eliminare il maggior numero possibile di NULL. L'ottimizzatore funziona molto più velocemente con queste informazioni e se puoi eliminare tutti i NULL in una tabella puoi recuperare una notevole quantità di spazio di archiviazione.

So che le prestazioni non sono qualcosa a cui i DBA pensano così tanto, ma puoi solo lanciare una quantità limitata di memoria e potenza del processore in una soluzione, un punto che dovrai iniziare a pensare al design logico e fisico .

Nota anche che questo è solo per RDBMS reali e sto basando la parte tecnica delle mie risposte su SQL Server. Anche il T-SQL elencato per trovare colonne nullable senza null proviene da SQL Server.


1
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Paul White
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.