Perché un vincolo UNIQUE consente un solo NULL?


36

Tecnicamente, NULL = NULL è False, secondo quella logica nessun NULL è uguale a qualsiasi NULL e tutti i NULL sono distinti. Ciò non dovrebbe implicare che tutti i NULL sono univoci e un indice univoco dovrebbe consentire un numero qualsiasi di NULL?


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Paul White dice GoFundMonica

Risposte:


52

Perché funziona in questo modo? Perché molto tempo fa, qualcuno ha preso una decisione di progettazione senza sapere o preoccuparsi di ciò che dice lo standard (dopotutto, abbiamo tutti i tipi di comportamenti strani con NULLs, e possiamo forzare comportamenti diversi a piacimento). Tale decisione imponeva che, in questo caso NULL = NULL,.

Non è stata una decisione molto intelligente. Quello che avrebbero dovuto fare è far sì che il comportamento predefinito aderisca allo standard ANSI e, se volessero davvero questo comportamento peculiare, consentirlo tramite un'opzione DDL come WITH CONSIDER_NULLS_EQUALo WITH ALLOW_ONLY_ONE_NULL.

Ovviamente, il senno di poi è 20/20.

E abbiamo una soluzione alternativa, ora, comunque, anche se non è la più pulita o intuitiva.

È possibile ottenere il comportamento ANSI corretto in SQL Server 2008 e versioni successive creando un indice univoco e filtrato.

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

Ciò consente più di un NULLvalore perché quelle righe sono completamente escluse dal controllo duplicato. Come bonus aggiuntivo, questo finirebbe per essere un indice più piccolo di uno che consisteva dell'intera tabella se NULLfossero consentiti più messaggi (soprattutto quando non è l'unica colonna nell'indice, ha INCLUDEcolonne, ecc.). Tuttavia, potresti voler conoscere alcune delle altre limitazioni degli indici filtrati:


8

Corretta. L'implementazione di un vincolo o indice univoco nel server sql consente uno e un solo NULL. Inoltre correggi che questo tecnicamente non si adatta alla definizione di NULL, ma è una di quelle cose che hanno fatto per renderlo più utile anche se non è "tecnicamente" corretto. Nota un PRIMARY KEY (anche un indice univoco) non consente NULL (ovviamente).


1
Anche questo tecnicismo (di SQL Server) non si adatta allo standard SQL. C'è un articolo Connect di 7 anni su questo problema.
ypercubeᵀᴹ

@ypercube True. Ecco perché ho detto che era solo l'implementazione e non si adattava alla definizione di NULL. Non avevo pensato all'indice univoco filtrato (anche se l'ho usato per altre cose.)
Kenneth Fisher,

3

Prima di tutto, smetti di usare la frase "Valore Nullo", ti porterà fuori strada. Invece, usa la frase "marcatore null" - un marcatore in una colonna che indica che il valore effettivo in questa colonna è mancante o inapplicabile (ma nota che il marcatore non dice quale di queste opzioni è effettivamente il caso¹).

Ora, immagina quanto segue (in cui il database non ha una conoscenza completa della situazione modellata).

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

La regola di integrità che stiamo modellando è "il codice deve essere unico". La situazione del mondo reale viola questo, quindi il database non dovrebbe consentire a entrambi gli elementi 2 e 4 di essere nella tabella contemporaneamente.

L'approccio più sicuro e meno flessibile sarebbe quello di non consentire marcatori null nel campo Codice, quindi non vi è alcuna possibilità di dati incoerenti. L'approccio più flessibile sarebbe quello di consentire più marcatori null e preoccuparsi dell'unicità quando vengono inseriti i valori.

I programmatori Sybase hanno seguito l'approccio un po 'sicuro e non molto flessibile di consentire solo un marcatore null nella tabella - qualcosa che i commentatori si sono lamentati da allora. Microsoft ha continuato questo comportamento, immagino per la retrocompatibilità.


¹ Sono sicuro di aver letto da qualche parte che Codd ha considerato l'implementazione di due marcatori null - uno per sconosciuto, uno per inapplicabile - ma l'ho rifiutato, ma non riesco a trovare il riferimento. Ricordo bene?

PS La mia citazione preferita su null: Louis Davidson, "Progettazione di database di SQL Server 2000 professionale", Wrox Press, 2001, pagina 52. "In poche parole: NULL è cattivo".


1
Consentire un singolo nullnon raggiunge neanche questo obiettivo. Perché il valore mancante potrebbe risultare uguale al valore in una delle altre righe.
Martin Smith,

1
Cosa ha detto @MartinSmith. Cosa succede se si dispone di un vincolo di controllo CHECK (Value IN ('A','B','C','D'))? Quindi sia l'implementazione di SQL Server sia lo standard SQL consentono alla tabella di avere 5 righe (una riga per ogni valore più 1 con NULL.) Quindi, probabilmente, mentre il database è coerente con i suoi vincoli, non è coerente con l'intento del designer per la tabella deve avere un massimo di 4 righe. Non esiste alcun valore in cui NULL possa essere modificato in modo da non violare un vincolo, a meno che una o più righe non vengano eliminate.
ypercubeᵀᴹ

1
Il fatto che lo standard consentirebbe 6 anche 106 righe invece di 5 non cambia che entrambi falliscono in qualche modo in questo scenario.
ypercubeᵀᴹ

@Martin Smith, potrebbe, ma poi potrebbe non esserlo - il server di database non può dirlo, quindi non lo rischia e prende la strada sicura. È quello che hanno deciso i programmatori di Sybase (presumo), causando fastidio da allora (almeno fino a Inside SQL Server 6.5, il libro più antico sul mio scaffale, dove Ron Soukup fa lo stesso commento di Aaron Bertrand nella sua risposta) . Immagino che potrebbe essere peggio - non avrebbero potuto imporre marcatori nulli. :-)
Greenstone Walker,

2
@GreenstoneWalker - Non prende il percorso "sicuro". Presuppone che il valore mancante non sia in conflitto. CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;genererà un errore. Secondo la tua teoria delle motivazioni progettuali, avrebbe dovuto impedire l'inserimento NULLnel primo caso, poiché la conoscenza incompleta significa che non esiste alcuna garanzia che il valore sia diverso.
Martin Smith,

2

Questo potrebbe non essere tecnicamente accurato, ma filosoficamente mi aiuta a dormire la notte ...

Come molti altri hanno già detto o accennato, se si considera NULL come sconosciuto, non è possibile determinare se un valore NULL sia effettivamente uguale a un altro valore NULL. Pensandolo in questo modo, l'espressione NULL == NULL dovrebbe valutare NULL, che significa sconosciuto.

Un vincolo univoco avrebbe bisogno di un valore definitivo per il confronto dei valori di colonna. In altre parole, quando si confronta un singolo valore di colonna con qualsiasi altro valore di colonna usando l'operatore di uguaglianza, deve essere considerato falso per essere valido. L'ignoto non è veramente falso anche se è spesso trattato come un falso. Due valori NULL potrebbero essere uguali o no ... semplicemente non possono essere determinati in modo definitivo.

Aiuta a pensare a un vincolo unico come a valori restrittivi che possono essere determinati per essere distinti l'uno dall'altro. Quello che intendo con questo è se si esegue un SELECT che assomiglia a questo:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

La maggior parte delle persone si aspetterebbe un risultato, dato che esiste un vincolo unico. Se si consentissero più valori NULL in ColumnWithUniqueConstraint, sarebbe impossibile selezionare una singola riga distinta dalla tabella utilizzando NULL come valore confrontato.

Detto questo, credo che indipendentemente dal fatto che sia implementato in modo accurato rispetto alla definizione di NULL, è sicuramente molto più pratico nella maggior parte delle situazioni rispetto al consentire più valori NULL.


La tua selezione darà 1 risultato, quando c'è un vincolo Unico (in qualsiasi implementazione, non solo SQL Server). Qual è il tuo punto?
ypercubeᵀᴹ

-3

Uno degli scopi principali di un UNIQUEvincolo è prevenire record duplicati. Se è necessario disporre di una tabella in cui possono essere presenti più record in cui un valore è "sconosciuto", ma non è consentito che due record abbiano lo stesso valore "noto", è necessario assegnare ai valori sconosciuti identificatori univoci artificiali prima di aggiunto alla tabella.

Ci sono alcuni rari casi in cui una colonna che ha un UNIQUEvincolo e contiene un singolo valore nullo; ad esempio, se una tabella contiene una mappatura tra i valori di colonna e le descrizioni di testo localizzate, una riga per NULLconsentirebbe di definire la descrizione che dovrebbe apparire quando quella colonna in un'altra tabella lo è NULL. Il comportamento di NULLconsente per quel caso d'uso.

Altrimenti, non vedo alcuna base per un database con un UNIQUEvincolo su qualsiasi colonna per consentire l'esistenza di molti record identici, ma non vedo alcun modo per impedirlo pur consentendo più record i cui valori chiave non sono distinguibili. Dichiarare che NULLnon è uguale a se stesso non renderà i NULLvalori distinguibili l'uno dall'altro.


3
Gli identificatori univoci artificiali sono uno scherzo, mi dispiace. Come hai intenzione di farlo per un VIN? Se non sai di cosa si tratta, perché inventare qualcosa? Solo per occupare spazio su disco aggiuntivo? Sembra una sciocchezza aggirare qualche altro problema (come non voler scrivere l'applicazione in modo tale che gestisca con grazia NULL). Se hai assolutamente bisogno di sapere perché qualcosa è NULL (esiste ma ignoto vs. sappi che non esiste vs. non sapere o preoccuparti se esiste, per esempio), quindi aggiungi una sorta di colonna di stato. I token portano semplicemente a un codice scomodo da gestire.
Aaron Bertrand

Molto dipende dallo scopo del vincolo di unicità. Se un campo verrà utilizzato come identificatore, non dovrebbe essere nullo. Nei casi (come nei VIN) in cui le regole aziendali suggeriscono che quando un articolo appare due volte, uno di essi deve essere sbagliato, ma alcuni articoli potrebbero essere "non conosciuti", un vincolo di unicità non sembra l'approccio corretto. Se uno ha un veicolo con un VIN noto e è in conflitto con un altro nel database, è possibile sapere che almeno uno dei VIN è errato, ma sarebbe meglio che il database riportasse il valore ritenuto per entrambi i record piuttosto che indovinare quello è giusto.
supercat,

@AaronBertrand: ci sono alcuni casi in cui un campo univoco-se-non-null possibilmente nullo dovrebbe essere una chiave surrogata che non può essere stabilito prima di popolare il campo (ad esempio "ID coniuge"), ma in situazioni come che un vincolo "unico" sarebbe insufficiente; sarebbe necessario che se X.Spouse fosse non nullo, X.Spouse.Spouse = X. Per inciso, qualcosa come "coniuge" potrebbe anche essere gestito dicendo che il record per una persona non sposata non dovrebbe avere "NULL" come coniuge, ma piuttosto un proprio ID, nel qual caso la regola X.spouse.spouse = X potrebbe si applicano a tutti.
supercat,
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.