Come creare un indice univoco su una colonna NULL?


101

Sto usando SQL Server 2005. Voglio vincolare i valori in una colonna in modo che siano univoci, pur consentendo NULLS.

La mia soluzione attuale prevede un indice univoco su una vista in questo modo:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

Qualche idea migliore?


16
nessuna possibilità di utilizzare sql 2008? puoi creare un indice filtrato usando 'where'
Simon_Weaver

3
Non intendevi unico, consentendo NULL , sembra che tu abbia inteso unico, ma includendo più NULL . Altrimenti, NULL è indicizzato come qualsiasi altro valore e il vincolo di unicità funziona come previsto, ma non secondo gli standard SQL, come @pst menzionato in un commento di seguito.
Suncat 2000,

Risposte:


26

Sono abbastanza sicuro che non puoi farlo, poiché viola lo scopo degli unici.

Tuttavia, questa persona sembra avere un lavoro decente in giro: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html


2
Sembra che il contenuto del link che hai fornito sia stato effettivamente (parzialmente) copiato senza attribuzione da qui: decipherinfosys.wordpress.com/2007/11/30/…
Tom Juergens

77
Non sono d'accordo sul fatto che "viola lo scopo degli utenti unici": NULL è un valore speciale in SQL (simile in molti modi a NaN) e deve essere trattato di conseguenza. In realtà è un fallimento in SQL Server per onorare varie specifiche SQL: ecco un link per una richiesta per la "corretta implementazione" per quello che vale: connect.microsoft.com/SQLServer/feedback/details/299229/… .

5
per riferimento nel 2008 su puoi fare CREATE UNIQUE INDEX foo ON dbo.bar (key) WHERE key NOT NULL;
niico

2
Non sono d'accordo anche con "viola lo scopo degli unici", NULL non è uguale a NULL, quindi dovresti essere in grado di creare un indice univoco su una colonna nullable e inserire più null.
Wodzu

105

Utilizzando SQL Server 2008, è possibile creare un indice filtrato: http://msdn.microsoft.com/en-us/library/cc280372.aspx . (Vedo che Simon ha aggiunto questo come commento, ma ho pensato che meritasse una risposta perché il commento è facilmente sfuggito.)

Un'altra opzione è un trigger per verificare l'unicità, ma ciò potrebbe influire sulle prestazioni.


84
create unique index UIX on MyTable (Column1) where Column1 is not null
Jørn Schou-Rode

1
Nota: attualmente SQL Server Management Studio non sembra sapere come creare tali indici, quindi se in seguito modifichi la tabella si confonderà e proverà a rilasciarla, quindi ricordati di ricrearla
Simon_Weaver

3
Sembra che Microsoft abbia aggiornato SSMS per supportare questo. Ho SSMS 10.50.1617 e nella finestra di dialogo Proprietà indice è possibile selezionare la pagina Filtro per modificare il filtro. es. "([Colonna1] NON È NULLO)"
Phil Haselden

5
Consentire più valori nulli in un indice e filtrare i valori nulli da un indice sono cose separate. Il filtraggio di un indice in realtà esclude i record dall'indice, mentre le altre soluzioni trasformano il valore null in un valore univoco utile. Sii consapevole della differenza.
Suncat 2000,

Se stai usando stored procedure su una tabella con un indice filtrato come quello, assicurati che lo ANSI_NULLSsia ON, altrimenti riceverai un errore quando provi a inserire dati.
Arne

71

Il trucco della colonna calcolata è ampiamente noto come "nullbuster"; le mie note accreditano Steve Kass:

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)

Sembra un bel trucco. Stranamente la ricerca di nullbuster non porta troppe cose. Mi chiedo se questo sarà utile anche per accelerare le ricerche - piuttosto che una colonna calcolata di solo 1 e 0 per null o no, se l'uso del PK dà all'indice qualcosa in più con cui lavorare? Andando a testare questo fine settimana su un grande tavolo e vedere.
David Storfer

@DavidStorfer, non puoi farlo perché potresti avere una collisione tra gli ID delle due diverse tabelle.
user393274

Miglioramento: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz

5
@ Faiz: il miglioramento è negli occhi di chi guarda. Preferisco l'aspetto dell'originale.
giorno del

@NunoG, questa dovrebbe essere la risposta accettata poiché fornisce una buona soluzione conforme alle tue esigenze, invece di collegare semplicemente un sito esterno che potrebbe scomparire.
Frédéric

-3

A rigor di termini, una colonna nullable univoca (o un insieme di colonne) può essere NULL (o un record di NULL) solo una volta, poiché avere lo stesso valore (e questo include NULL) più di una volta ovviamente viola il vincolo univoco.

Tuttavia, ciò non significa che il concetto di "colonne nullable univoche" sia valido; per implementarlo effettivamente in qualsiasi database relazionale dobbiamo solo tenere a mente che questo tipo di database deve essere normalizzato per funzionare correttamente, e la normalizzazione di solito comporta l'aggiunta di diverse tabelle extra (non entità) per stabilire relazioni tra le entità .

Facciamo un esempio di base considerando solo una "colonna nullable univoca", è facile espanderla a più colonne di questo tipo.

Supponiamo di avere le informazioni rappresentate da una tabella come questa:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

Possiamo farlo mettendo da parte uniqnull e aggiungendo una seconda tabella per stabilire una relazione tra i valori uniqnull e the_entity (piuttosto che avere uniqnull "dentro" the_entity):

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

Per associare un valore di uniqnull a una riga in the_entity dobbiamo aggiungere anche una riga in the_relation.

Per le righe in the_entity in cui non sono associati valori uniqnull (cioè per quelle che metteremmo NULL in the_entity_incorrect) semplicemente non aggiungiamo una riga in the_relation.

Si noti che i valori per uniqnull saranno univoci per tutta la_relazione, e si noti anche che per ogni valore in_entità può esserci al massimo un valore in_relazione, poiché le chiavi primaria ed esterna su di essa lo impongono.

Quindi, se un valore di 5 per uniqnull deve essere associato a un the_entity id di 3, dobbiamo:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

E, se un valore id di 10 per the_entity non ha una controparte uniqnull, facciamo solo:

start transaction;
insert into the_entity (id) values (10); 
commit;

Per denormalizzare queste informazioni e ottenere i dati che una tabella come the_entity_incorrect potrebbe contenere, dobbiamo:

select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

L'operatore "left outer join" assicura che tutte le righe di the_entity appariranno nel risultato, mettendo NULL nella colonna uniqnull quando non sono presenti colonne corrispondenti in the_relation.

Ricorda, qualsiasi sforzo speso per alcuni giorni (o settimane o mesi) nella progettazione di un database ben normalizzato (e le corrispondenti viste e procedure denormalizzate) ti farà risparmiare anni (o decenni) di dolore e risorse sprecate.


6
Come già affermato nel commento della risposta accettata con cinquanta voti positivi, dovrebbe essere supportato da MS Sql Server per avere più null in una colonna indicizzata come unica. È un fallimento nell'implementazione degli standard SQL non consentirlo. Null non è un valore, null non è uguale a null, che è una regola SQL di base da anni. Quindi la tua prima frase è sbagliata e la maggior parte dei lettori non si preoccuperà di continuare a leggere.
Frédéric
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.