2009-03-23 10 views
8

Ecco un piccolo esperimento che ho eseguito in un database Oracle (10g). A prescindere dalla convenienza dell'implementazione (Oracle), non riesco a capire perché alcuni inserimenti siano accettati e altri rifiutati.Come posso vincolare più colonne per evitare duplicati, ma ignorare valori nulli?

create table sandbox(a number(10,0), b number(10,0)); 
create unique index sandbox_idx on sandbox(a,b); 

insert into sandbox values (1,1); -- accepted 
insert into sandbox values (1,2); -- accepted 
insert into sandbox values (1,1); -- rejected 

insert into sandbox values (1,null); -- accepted 
insert into sandbox values (2,null); -- accepted 
insert into sandbox values (1,null); -- rejected 

insert into sandbox values (null,1); -- accepted 
insert into sandbox values (null,2); -- accepted 
insert into sandbox values (null,1); -- rejected 

insert into sandbox values (null,null); -- accepted 
insert into sandbox values (null,null); -- accepted 

Supponendo che ha senso avere di tanto in tanto alcune righe con qualche colonna di valori ignoti, posso pensare a due possibili casi d'uso che coinvolgono i duplicati impedendo:
1. Voglio respingere i duplicati, ma accettare quando qualsiasi vincolata il valore della colonna è sconosciuto.
2. Voglio rifiutare i duplicati, anche nei casi in cui il valore di una colonna vincolata è sconosciuto.

Apparentemente Oracle implementa qualcosa di diverso però:
3. Rifiuta duplicati, ma accetta (solo) quando tutti valori delle colonne vincolate sono sconosciuti.

Sono in grado di pensare a come utilizzare l'implementazione di Oracle per utilizzare case (2), ad esempio, avere un valore speciale per "unknown" e rendere le colonne non annullabili. Ma non riesco a capire come usare il caso (1).

In altre parole, come posso convincere Oracle ad agire in questo modo?

create table sandbox(a number(10,0), b number(10,0)); 
create unique index sandbox_idx on sandbox(a,b); 

insert into sandbox values (1,1); -- accepted 
insert into sandbox values (1,2); -- accepted 
insert into sandbox values (1,1); -- rejected 

insert into sandbox values (1,null); -- accepted 
insert into sandbox values (2,null); -- accepted 
insert into sandbox values (1,null); -- accepted 

insert into sandbox values (null,1); -- accepted 
insert into sandbox values (null,2); -- accepted 
insert into sandbox values (null,1); -- accepted 

insert into sandbox values (null,null); -- accepted 
insert into sandbox values (null,null); -- accepted 
+0

Perfetto esempio di una buona domanda (più è quello che ho bisogno di risposto!) – orbfish

risposta

7
create unique index sandbox_idx on sandbox 
(case when a is null or b is null then null else a end, 
    case when a is null or b is null then null else b end); 

Un indice funzionale! Fondamentalmente ho solo bisogno di assicurarmi che tutte le tuple che voglio ignorare (cioè accettare) siano tradotte in tutti i null. Brutto, ma non brutto. Funziona come desiderato

capito con l'aiuto di una soluzione ad un'altra domanda: How to constrain a database table so only one row can have a particular value in a column?

Quindi, andare lì e dare Tony Andrews sottolinea anche.:)

+0

Non lo trovo affatto brutto. Molto più pulito IMHO della risposta accettata, che può mescolare 2 colonne insieme, forse nemmeno lo stesso tipo di dati, per creare qualche chiave unica di Frankenstein (non che io non l'avrei usata se non mi avessi mostrato la sintassi corretta per il multicolumn). – orbfish

1

Immagino che tu possa farlo.

Solo per la cronaca, però, lascio il mio punto di spiegare il motivo per cui Oracle si comporta come che se si dispone di un semplice indice univoco su due colonne:

Oracle non accetterà mai due (1, null) coppie se le colonne sono indicizzati in modo univoco.

Una coppia di 1 e null, è considerata una coppia "indicizzabile". Non è possibile indicizzare una coppia di due null, è per questo che consente di inserire tutte le coppie null e null che desideri.

(1, null) viene indicizzato perché 1 può essere indicizzato. La prossima volta che provi a inserire (1, null) di nuovo, 1 viene prelevato dall'indice e il vincolo univoco viene violato.

(null, null) non è indicizzato perché non esiste alcun valore da indicizzare. Ecco perché non viola il vincolo univoco.

+0

Questo è solo uno dei motivi per la realizzazione di indici basati su funzioni in Oracle. Permette all'azienda di adattare l'indice alle proprie regole di business. – DCookie

+0

Sì, sono corretto :-) – Petros

7

Prova un indice basato su funzioni:

creare unica sandbox_idx indice su sandbox (caso in cui un IS NULL ALLORA NULL quando B è NULL THEN ELSE NULL un || '' || b END);

Ci sono altri modi per skinare questo gatto, ma questo è uno di questi.

2

Non sono un ragazzo Oracle, ma ecco un'idea che dovrebbe funzionare, se è possibile includere una colonna calcolata in un indice in Oracle.

Aggiungi una colonna aggiuntiva al tuo tavolo (e al tuo indice UNIQUE) che viene calcolata come segue: è NULL se sia a che b sono non NULL, ed è altrimenti la chiave primaria della tabella. Io chiamo questa colonna aggiuntiva "nullbuster" per ovvi motivi.

alter table sandbox add nullbuster as 
    case when a is null or b is null then pk else null end; 
create unique index sandbox_idx on sandbox(a,b,pk); 

ho dato questo esempio un certo numero di volte intorno al 2002 o giù di lì nel microsoft.public.sqlserver.programming gruppo Usenet. Puoi trovare le discussioni se cerchi groups.google.com per la parola "nullbuster". Il fatto che stai usando Oracle non dovrebbe avere molta importanza.

P.S. In SQL Server, questa soluzione è praticamente sostituite da indici filtrati:

create unique index sandbox_idx on sandbox(a,b) 
(where a is not null and b is not null); 

Il filo si fa riferimento suggerisce che Oracle non ti dà questa opzione. Non ha anche la possibilità di una vista indicizzata, che è un'altra alternativa?

create view sandbox_for_unique as 
select a, b from sandbox 
where a is not null and b is not null; 

create index sandbox_for_unique_idx on sandbox_for_unique(a,b); 
+0

Buona risposta anche se un po 'troppo barocca per la mia applicazione. Per rispondere alle tue domande, Oracle non ha indici filtrati ma la tua "vista indicizzata" sembra essere coperta da viste materializzate in Oracle, che possono essere indicizzate e spesso sono per l'integrità referenziale. – orbfish

Problemi correlati