Pregunta

Aquí hay un pequeño experimento me encontré en una base de datos Oracle (10g). Aparte de la comodidad de implementación (de Oracle), no puedo entender por qué se aceptan algunas inserciones y otros rechazaron.

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

Si se asume que tiene sentido tener ocasionalmente algunas filas con algunos valores de columna desconocido, puedo pensar en dos posibles casos de uso relacionados con la prevención de duplicados:
 1. Quiero rechazar duplicados, pero acepta cuando el valor de cualquier columna restringida es desconocida.
 2. Quiero rechazar duplicados, incluso en los casos en que el valor de una columna restringida es desconocida.

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

No puedo pensar en maneras de hacer uso de la implementación de Oracle para obtener usar el caso (2) - por ejemplo, tienen un valor especial para el "desconocido", y hacer que las columnas no anulable. Pero no puedo encontrar la manera de llegar a utilizar el caso (1).

En otras palabras, ¿cómo puedo obtener Oracle para actuar de esta manera?

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
¿Fue útil?

Solución 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 índice funcional! Básicamente Sólo tenía que asegurarse de que todas las tuplas que quiero hacer caso (es decir, - aceptar) se traducen a todos los valores nulos. Feo, pero no Extremo feo. Funciona como se desea.

figurado a cabo con la ayuda de una solución a otra pregunta: Cómo restringir una tabla de base de datos para una sola fila puede tener un valor particular en una columna?

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

Otros consejos

Pruebe con 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 maneras de pelar este gato, pero éste es uno de ellos.

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

Añadir una columna adicional a su mesa (y su índice UNIQUE) que se calcula de la siguiente manera: es NULL si tanto A como B son no nulo, y es la clave primaria de la tabla de otra manera. Yo llamo a esto la columna "nullbuster" adicional 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, un número de veces alrededor de 2002 más o menos en el microsoft.public.sqlserver.programming grupo Usenet. Puede encontrar las discusiones si se busca groups.google.com para la palabra "nullbuster". El hecho de que usted está utilizando Oracle no debería importar demasiado.

.

P.S En SQL Server, esta solución es más o menos superado 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. ¿También no tiene la posibilidad de una vista indizada, 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);

Creo que se puede a continuación.

Sólo para que conste, sin embargo, les dejo mi párrafo para explicar por qué se comporta como Oracle 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 forma única.

Un par de 1 y un valor nulo, se considera un par "indexable". Un par de dos nulos no pueden ser indexados, es por eso que le permite insertar hasta nula, pares nulos como desee.

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top