2009-03-23 10 views
8

Aquí hay un pequeño experimento que ejecuté en una base de datos Oracle (10g). Aparte de la conveniencia de la implementación de (Oracle), no puedo entender por qué algunas inserciones son aceptadas y otras rechazadas.¿Cómo puedo restringir varias columnas para evitar duplicados, pero ignorar los valores nulos?

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 

Asumiendo que tiene sentido tener ocasionalmente algunas filas con valores desconocidos alguna columna, puedo pensar en dos posibles casos de uso relacionados con la prevención de duplicados:
1. Quiero rechazar duplicados, pero aceptar cuando cualquier restringido el valor de la columna es desconocido.
2. Deseo rechazar los duplicados, incluso en los casos en que se desconoce el valor de una columna restringida.

Al parecer Oracle implementa algo diferente, sin embargo:
3. Rechazar duplicados, pero aceptan (sólo) cuando todos los valores de las columnas restringidos son desconocidos.

Puedo pensar en maneras de utilizar la implementación de Oracle para llegar al caso de uso (2), por ejemplo, tener un valor especial para "desconocido" y hacer que las columnas no sean nulas. Pero no puedo entender cómo usar el caso (1).

En otras palabras, ¿cómo puedo conseguir que Oracle actúe así?

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

ejemplo perfecto de una buena pregunta (y además es uno respondí que necesitaba!) – orbfish

Respuesta

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 índice funcional! Básicamente solo necesitaba asegurarme de que todas las tuplas que quiero ignorar (es decir, aceptar) se traducen a todos los nulos. Feo, pero no feo. Funciona según lo deseado.

figurado a cabo con la ayuda de una solución a otra pregunta: How to constrain a database table so only one row can have a particular value in a column?

Así que ir allí y dar a Tony Andrews señala también.:)

+0

No me parece feo. Mucho más limpio en mi humilde opinión que la respuesta aceptada, que puede acumular 2 columnas juntas, tal vez ni siquiera el mismo tipo de datos, para crear alguna clave única de Frankenstein (no es que no lo hubiera usado si no me hubieras mostrado la sintaxis correcta para multicolumnas) – orbfish

1

I guess you can then.

Sólo para que conste, sin embargo, les dejo mi párrafo para explicar por qué Oracle se comporta como que si usted tiene un índice único simple en dos columnas:

Oracle nunca aceptar dos (1, null) pares si las columnas están indexados de manera única.

Un par de 1 y un nulo, se considera un par "indexable". Un par de dos valores nulos no se puede indexar, es por eso que le permite insertar tantos pares nulos como quiera.

(1, nulo) se indexa porque se puede indexar 1. La próxima vez que intente insertar (1, nulo) de nuevo, 1 es recogido por el índice y se viola la restricción única.

(nulo, nulo) no está indexado porque no hay ningún valor para indexar. Es por eso que no viola la restricción única.

+0

Esto es sólo una de las razones para la implementación de índices basados ​​en funciones en Oracle. Permite a la empresa adaptar el índice a sus propias reglas comerciales. – DCookie

+0

Sí, estoy corregido :-) – Petros

7

Prueba un índice basado en la función:

crear sandbox_idx índice único en caja de arena (CASE cuando A es NULL ENTONCES NULL cuando B es NULL ENTONCES NULL ELSE a || '' || b END);

Hay otras formas de desollar a este gato, pero esta es una de ellas.

2

No soy un tipo de Oracle, pero esta es una idea que debería funcionar, si puede incluir una columna calculada en un índice en Oracle.

Agregue una columna adicional a su tabla (y su índice UNIQUE) que se calcula de la siguiente manera: es NULL si a y b no son NULL, y es la clave principal de la tabla de lo contrario. Llamo a esta columna adicional "nullbuster" por razones obvias.

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

me dio este ejemplo varias veces alrededor de 2002 más o menos en el microsoft.public.sqlserver.programming grupo Usenet. Puede encontrar las discusiones si busca en groups.google.com la palabra "nullbuster". El hecho de que estés usando Oracle no debería importar mucho.

P.S. En SQL Server, esta solución es más o menos sustituidas por los índices filtrados:

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

El hilo al que hizo referencia sugiere que Oracle no le da esta opción. ¿Tampoco tiene la posibilidad de una vista indexada, que es otra 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

Buena respuesta, aunque un poco demasiado barroca para mi aplicación. Para responder a sus preguntas, Oracle no tiene índices filtrados, pero su "vista indexada" parece estar cubierta por vistas materializadas en Oracle, que pueden indexarse ​​y, a menudo, son de integridad referencial. – orbfish

Cuestiones relacionadas