Comment puis-je contraindre plusieurs colonnes pour éviter les doublons, mais ignorer les valeurs nulles?

StackOverflow https://stackoverflow.com/questions/675398

Question

Voici une petite expérience, je courais dans une base de données Oracle (10g). Mis à part la facilité de mise en œuvre (Oracle), je ne peux pas comprendre pourquoi certaines insertions sont acceptées et d'autres rejetés.

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 des valeurs de colonne inconnue, je peux penser à deux cas d'utilisation possibles impliquant la prévention des doublons:
 1. Je veux rejeter les doublons, mais accepter lorsque la valeur de toute colonne contrainte est inconnue.
 2. Je veux 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 accepter (uniquement) lorsque tous valeurs de la colonne ne sont pas connues limitées.

Je peux penser à des façons de faire usage de la mise en œuvre d'Oracle à l'occasion d'utiliser le cas (2) - par exemple, ont une valeur particulière pour « inconnu », et que les colonnes non annulable. Mais je ne peux pas comprendre comment arriver à utiliser le cas (1).

En d'autres termes, comment puis-je Oracle d'agir comme ça?

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
Était-ce utile?

La solution 2

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! En fait, je voulais juste pour vous assurer que tous les tuples que je veux ignorer (c.-à - accepter) se traduit à tous les nulls. Laid, mais pas buter laid. Fonctionne comme on le souhaite.

figured it out à l'aide d'une solution à une autre question: Comment contraindre une table de base de données pour une seule ligne peut avoir une valeur particulière dans une colonne

Alors, allez-y et donner des points Tony Andrews aussi. :)

Autres conseils

Essayez un indice fondé 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 la peau ce chat, mais c'est l'un d'entre eux.

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

Ajouter une colonne supplémentaire à votre table (et votre index UNIQUE) qui est calculé comme suit: il est NULL si a et b sont non-NULL, et il est par ailleurs la clé primaire de la table. 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);

J'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 utilisez Oracle ne devrait pas beaucoup d'importance.

.

P.S Dans SQL Server, cette solution est à peu près supplanté par 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. At-il pas non plus 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);

Je suppose que vous pouvez alors.

Pour la petite histoire bien, 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ées de manière unique.

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

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

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top