2009-03-23 9 views
8

Voici une petite expérience que j'ai courue dans une base de données Oracle (10g). Mis à part la commodité d'implémentation (Oracle), je ne peux pas comprendre pourquoi certaines insertions sont acceptées et d'autres rejetées.Comment puis-je contraindre plusieurs colonnes pour éviter les doublons, mais ignorer les valeurs nulles?

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 

En supposant qu'il est logique d'avoir de temps en temps quelques lignes avec une colonne de valeurs inconnues, je peux penser à deux cas d'utilisation possibles impliquant la prévention des doublons:
1. Je veux rejeter les doublons, mais accepter quand une contrainte La valeur de la colonne est inconnue.
2. Je souhaite rejeter les doublons, même dans les cas où la valeur d'une colonne contrainte est inconnue.

Apparemment, Oracle met en œuvre quelque chose de différent si:
3. Rejeter les doublons, mais accept (uniquement) lorsque toutes les valeurs de la colonne ne sont pas connus contraintes.

Je peux penser à des façons d'utiliser l'implémentation d'Oracle pour utiliser case (2) - par exemple, avoir une valeur spéciale pour "unknown", et rendre les colonnes non-nulles. Mais je n'arrive pas à comprendre comment utiliser le cas (1). En d'autres termes, comment Oracle peut-il agir comme cela?

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

exemple parfait d'une bonne question (plus il est celui que je avais besoin répondu!) – orbfish

Répondre

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 fonctionnel! Fondamentalement, j'ai juste besoin de m'assurer que tous les tuples que je veux ignorer (ie - accepter) soient traduits en tous les nulls. Laid mais pas moche. Fonctionne comme désiré.

figured it out à l'aide d'une solution à une autre question: How to constrain a database table so only one row can have a particular value in a column?

donc aller là-bas et donner des points Tony Andrews aussi.:)

+0

Je ne trouve pas cela moche du tout. Beaucoup plus propre à mon avis que la réponse acceptée, qui peut combiner 2 colonnes, peut-être même pas le même type de données, pour créer une clé unique Frankenstein (pas que je ne l'aurais pas utilisé si vous ne m'aviez pas montré la syntaxe correcte pour multicolonne). – orbfish

1

Je suppose que vous pouvez alors.

Juste pour le dossier si, je quitte mon paragraphe pour expliquer pourquoi Oracle se comporte comme si vous avez un simple index unique sur deux colonnes:

Oracle n'acceptera jamais deux (1, null) paires si les colonnes sont indexés de manière unique.

Une paire de 1 et une valeur nulle sont considérées comme une paire «indexable». Une paire de deux null ne peut pas être indexée, c'est pourquoi il vous permet d'insérer autant de paires nulles et nulles que vous le souhaitez.

(1, null) est indexé car 1 peut être indexé. La prochaine fois que vous essayez d'insérer à nouveau (1, null), 1 est récupéré par l'index et la contrainte unique est violée.

(null, null) n'est pas indexé car il n'y a pas de valeur à indexer. C'est pourquoi il ne viole pas la contrainte unique.

+0

Ceci est juste l'une des raisons pour la mise en œuvre des indices basés sur des fonctions dans Oracle. Cela permet à l'entreprise d'adapter l'indice à ses propres règles métier. – DCookie

+0

Oui, je suis corrigé :-) – Petros

7

Essayez un indice basé sur les fonctions:

créer un index unique sandbox_idx sur Sandbox (CASE Lorsqu'un IS NULL ALORS NULL lorsque b est NULL NULL ALORS SINON un || '' || b FIN);

Il y a d'autres façons de peler ce chat, mais c'est l'un d'entre eux.

2

Je ne suis pas un type Oracle, mais voici une idée qui devrait fonctionner, si vous pouvez inclure une colonne calculée dans un index dans Oracle.

Ajoutez une colonne supplémentaire à votre table (et votre index UNIQUE) qui est calculée comme suit: elle est NULL si a et b sont non-NULL, et c'est la clé primaire de la table sinon. J'appelle cette colonne supplémentaire "nullbuster" pour des raisons évidentes.

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); 

je lui ai donné cet exemple un certain nombre de fois autour de 2002 ou si dans le groupe Usenet microsoft.public.sqlserver.programming. Vous pouvez trouver les discussions si vous recherchez groups.google.com pour le mot "nullbuster". Le fait que vous utilisiez Oracle ne devrait pas avoir beaucoup d'importance.

P.S. Dans SQL Server, cette solution est à peu près supplantées par des index filtrés:

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

Le fil que vous avez mentionné suggère que Oracle ne vous donne pas cette option. N'a-t-il pas aussi la possibilité d'une vue indexée, ce qui est une autre alternative?

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

Bonne réponse bien qu'un peu trop baroque pour mon application. Pour répondre à vos questions, Oracle n'a pas d'index filtrés, mais votre «vue indexée» semble être couverte par des vues matérialisées dans Oracle, qui peuvent être indexées et sont souvent destinées à l'intégrité référentielle. – orbfish

Questions connexes