Pergunta

Aqui está uma pequena experiência eu corri em um banco de dados Oracle (10g). Além da conveniência de implementação (da Oracle), eu não consigo descobrir por que algumas inserções são aceitos e outros rejeitados.

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

Assumindo que faz sentido ter ocasionalmente algumas linhas com alguns valores de coluna desconhecido, eu posso pensar em dois casos de uso possíveis envolvendo duplicatas impedindo:
1. Eu quero rejeitar duplicatas, mas aceito quando o valor de qualquer coluna restrita é desconhecida.
2. Quero rejeitar duplicatas, mesmo nos casos em que o valor de uma coluna restrita é desconhecida.

Aparentemente o Oracle implementa algo diferente, porém:
3. Rejeitar duplicatas, mas aceito (somente) quando todas valores da coluna constrangidos são desconhecidos.

Não consigo pensar em maneiras de fazer uso da implementação da Oracle para chegar ao caso de uso (2) - por exemplo, têm um valor especial para "desconhecido", e tornar as colunas não-nulo. Mas eu não consigo descobrir como chegar ao caso de uso (1).

Em outras palavras, como posso obter Oracle para agir assim?

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
Foi útil?

Solução 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);

Um índice funcional! Basicamente, eu só precisava ter certeza de todas as tuplas que eu quero ignorar (ou seja, - aceitar) são traduzidos para todos os valores nulos. Feio, mas não intrometer feio. Funciona como desejado.

Descobri-lo com a ajuda de uma solução para outra pergunta: Como restringir uma tabela de banco de dados para que apenas uma linha pode ter um valor particular em uma coluna?

Então, vá lá e dar pontos Tony Andrews também. :)

Outras dicas

Tente um índice baseado em função:

criar sandbox_idx índice exclusivo na sandbox (caso quando uma for nulo, nulo quando b for nulo, NULL ENTÃO um || '' || b END);

Existem outras maneiras de esfolar este gato, mas este é um deles.

Eu não sou um cara Oracle, mas aqui está uma ideia que deve funcionar, se você pode incluir uma coluna computada em um índice em Oracle.

Adicionar uma coluna adicional para a sua mesa (e seu índice UNIQUE) que é calculado da seguinte forma: É NULL se ambos a e b são não-NULL, e é a chave primária da tabela de outra forma. Eu chamo isso de coluna "nullbuster" adicional por razões óbvias.

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

Eu dei este exemplo, um número de vezes por volta de 2002 ou assim na microsoft.public.sqlserver.programming grupo Usenet. Você pode encontrar as discussões se você procurar groups.google.com para a palavra "nullbuster". O fato de que você está usando o Oracle não deve importar muito.

.

P.S No SQL Server, esta solução é praticamente substituída por índices filtrados:

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

O fio que você referenciou sugere que a Oracle não lhe dá essa opção. Será que ela também não tem a possibilidade de uma exibição indexada, que é outra 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);

Eu acho que você pode, então.

Apenas para o registro, porém, deixo meu parágrafo para explicar por que a Oracle se comporta como que se você tiver um índice exclusivo simples em duas colunas:

A Oracle nunca aceitará dois (1, null) pares se as colunas são indexados exclusivamente.

Um par de 1 e um nulo, é considerado um par "indexável". Um par de dois nulos não podem ser indexados, é por isso que lhe permite inserir as nula, pares nulos como você gosta.

(1, null) fica indexado porque 1 pode ser indexado. Da próxima vez que você tentar inserir (1, null) novamente, 1 é captado pelo índice ea restrição exclusiva é violada.

(null, null) não é indexado porque não há valor a ser indexado. É por isso que não viola a restrição exclusiva.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top