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

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

Вопрос

Вот небольшой эксперимент, который я провел в базе данных 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, чтобы перейти к варианту использования (2) - например, присвоить специальное значение "неизвестно" и сделать столбцы ненулевыми.Но я не могу понять, как перейти к варианту использования (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
Это было полезно?

Решение 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);

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

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

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

Другие советы

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

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

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

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

Добавьте дополнительный столбец в вашу таблицу (и ваш УНИКАЛЬНЫЙ индекс), который вычисляется следующим образом:это значение равно НУЛЮ, если оба a и b не равны нулю, и в противном случае это первичный ключ таблицы.Я называю эту дополнительную колонку "nullbuster" по очевидным причинам.

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

Я думаю, тогда ты сможешь.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top