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