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.