Как я могу ограничить несколько столбцов, чтобы предотвратить дублирование, но игнорировать нулевые значения? - PullRequest
8 голосов
/ 24 марта 2009

Вот небольшой эксперимент, который я провел в базе данных Oracle (10g). Помимо удобства реализации (Oracle), я не могу понять, почему некоторые вставки принимаются, а другие отклоняются.

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

Предполагая, что иногда имеет смысл иметь несколько строк с неизвестными значениями столбцов, я могу вспомнить два возможных варианта использования, связанных с предотвращением дублирования: 1. Я хочу отклонить дубликаты, но принимаю, когда значение какого-либо ограниченного столбца неизвестно.
2. Я хочу отклонить дубликаты даже в тех случаях, когда значение ограниченного столбца неизвестно.

Очевидно, Oracle реализует нечто иное:
3. Отклонить дубликаты, но принять (только), когда все значения столбцов с ограничениями неизвестны.

Я могу придумать, как использовать реализацию Oracle для использования case (2) - например, иметь специальное значение для «unknown» и сделать столбцы необнуляемыми. Но я не могу понять, как использовать case (1).

Другими словами, как я могу заставить Oracle работать так?

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

Ответы [ 4 ]

7 голосов
/ 24 марта 2009

Попробуйте индекс на основе функций:

создать уникальный индекс sandbox_idx в песочнице (СЛУЧАЙ, КОГДА А НУЛ, ТО, НУЛЬ, КОГДА Б НУЛЕ, ТО, НУЛЬ, Иначе a || ',' || b END);

Есть и другие способы снять шкуру с этой кошки, но это один из них.

7 голосов
/ 24 марта 2009
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);

Функциональный индекс! По сути, мне просто нужно было убедиться, что все кортежи, которые я хочу игнорировать (то есть - принять), переведены на все нули. Уродливо, но не безобразно. Работает как нужно.

Разобрался с помощью решения другого вопроса: Как ограничить таблицу базы данных, чтобы только одна строка могла иметь конкретное значение в столбце?

Так что иди туда и дай Тони Эндрюсу очки тоже. :)

2 голосов
/ 10 августа 2009

Я не парень по Oracle, но вот идея, которая должна сработать, если вы можете включить вычисляемый столбец в индекс в Oracle.

Добавьте дополнительный столбец в вашу таблицу (и ваш UNIQUE-индекс), который вычисляется следующим образом: он равен NULL, если a и b не равны NULL, и в противном случае это первичный ключ таблицы. Я называю эту дополнительную колонку «нольбастер» по понятным причинам.

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

Я приводил этот пример несколько раз в 2002 году или около того в группе Usenet microsoft.public.sqlserver.programming. Вы можете найти обсуждения, если вы будете искать groups.google.com по слову «nullbuster». Тот факт, что вы используете Oracle, не должен иметь большого значения.

P.S. В SQL Server это решение в значительной степени заменено отфильтрованными индексами:

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

Поток, на который вы ссылались, предполагает, что Oracle не предоставляет вам эту опцию. Разве у него также нет возможности индексированного представления, что является еще одной альтернативой?

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);
1 голос
/ 24 марта 2009

Полагаю, вы сможете.

Только для справки, я оставляю свой параграф, чтобы объяснить, почему Oracle ведет себя так, если у вас есть простой уникальный индекс в двух столбцах:

Oracle никогда не примет две (1, нулевые) пары, если столбцы имеют уникальную индексацию.

Пара «1» и «ноль» считается «индексируемой» парой. Пара двух нулей не может быть проиндексирована, поэтому она позволяет вам вставлять столько нулевых, нулевых пар, сколько вам нужно.

(1, ноль) индексируется, потому что 1 может быть проиндексирован. В следующий раз, когда вы попытаетесь вставить (1, ноль) еще раз, индекс уловит 1, а ограничение уникальности нарушится.

(ноль, ноль) не индексируется, потому что нет значения для индексации. Вот почему это не нарушает уникальное ограничение.

...