Как создать уникальный индекс для столбца NULL?

Я использую SQL Server 2005. Я хочу, чтобы значения в столбце были уникальными, но разрешили NULLS.

Мое текущее решение включает уникальный индекс для такого вида:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

Есть идеи получше?


person Nuno G    schedule 10.10.2008    source источник
comment
нет шансов использовать sql 2008? вы можете создать отфильтрованный индекс, используя "где"   -  person Simon_Weaver    schedule 13.03.2010
comment
Вы не имели в виду уникальный, допускающий NULL, вы, похоже, имели в виду уникальный, но включающий несколько NULL. В противном случае NULL индексируется, как и любое другое значение, и ограничение уникальности работает, как ожидалось - только не в соответствии со стандартами SQL, как @pst упоминается в комментарии ниже.   -  person Suncat2000    schedule 09.02.2012


Ответы (5)


Совершенно уверен, что вы не можете этого сделать, так как это нарушает цель уникальности.

Однако у этого человека, похоже, есть достойная работа: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

person willasaywhat    schedule 10.10.2008
comment
Похоже, что содержимое предоставленной вами ссылки было фактически (частично) скопировано без указания авторства здесь: decipherinfosys.wordpress.com/2007/11/30/ - person Tom Juergens; 25.02.2010
comment
Я не согласен с тем, что это нарушает цель уникальности - NULL - это особое значение в SQL (во многом похожее на NaN) и требует соответствующего обращения. На самом деле в SQL Server не соблюдаются различные спецификации SQL: вот ссылка для запроса правильной реализации того, чего она стоит: connect.microsoft.com/SQLServer/feedback/details/299229/. - person ; 08.08.2010
comment
для справки в 2008 г. вы можете сделать CREATE UNIQUE INDEX foo ON dbo.bar (key) WHERE key IS NOT NULL; - person niico; 03.05.2017
comment
Я также не согласен с нарушением цели уникальности, NULL не равно NULL, поэтому вы должны иметь возможность создать уникальный индекс для столбца, допускающего значение NULL, и вставить несколько значений NULL. - person Wodzu; 08.02.2018
comment
Null не равно null - это педантично. null == null - ›IS NULL AND IS NULL, нет причин, по которым это не работает с ограничением уникальности - person Captain Prinny; 10.03.2021

Используя SQL Server 2008, вы можете создать отфильтрованный индекс: http://msdn.microsoft.com/en-us/library/cc280372.aspx. (Я вижу, что Саймон добавил это как комментарий, но подумал, что он заслуживает отдельного ответа, поскольку комментарий легко пропустить.)

Другой вариант - это триггер для проверки уникальности, но это может повлиять на производительность.

person Phil Haselden    schedule 07.07.2010
comment
Примечание: в настоящее время SQL Server Management Studio, похоже, не знает, как создавать такие индексы, поэтому, если вы позже измените таблицу, она запутается и попытается отбросить ее, поэтому не забудьте воссоздать ее. - person Simon_Weaver; 21.07.2011
comment
Похоже, что Microsoft обновила SSMS для поддержки этого. У меня SSMS 10.50.1617, и в диалоговом окне «Свойства индекса» вы можете выбрать страницу «Фильтр» для редактирования фильтра. например ([Столбец1] НЕ ПУСТОЙ) - person Phil Haselden; 09.08.2011
comment
Разрешение множественных нулей в индексе и фильтрация нулей из индекса - это разные вещи. Фильтрация индекса фактически исключает записи из индекса, тогда как другие решения преобразуют нуль в полезное уникальное значение. Знайте разницу. - person Suncat2000; 09.02.2012
comment
Если вы используете хранимые процедуры в таблице с подобным отфильтрованным индексом, убедитесь, что ANSI_NULLS равно ON, иначе вы получите ошибку при попытке вставить данные. - person Arne; 01.10.2012
comment
тогда как другие решения преобразуют нуль в полезное уникальное значение. Имейте в виду разницу - да, они исключены, но что, если столбец содержит несколько целых чисел. И pk - это какие-то другие ints. Если вы ищете, где ваш обнуляемый столбец = некоторый int, тогда метод nullbuster активно опасен - существует ненулевой шанс, что в обнуляемом столбце будет значение int, которое также присутствует в столбце pk, что потенциально позволяет вы полностью найдете не ту строку! Исключение строк, которые вы не хотите искать, безопаснее. - person Brian White; 03.12.2012

Уловка с вычисляемым столбцом широко известна как "уничтожение нуля"; мои записи кредитуют Стива Касса:

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
person onedaywhen    schedule 10.10.2008
comment
Это похоже на крутой трюк. Как ни странно, поиск nullbuster не вызывает лишних вопросов. Мне интересно, будет ли это полезно для ускорения поиска - вместо вычисляемого столбца всего 1 и 0 для нуля или нет, если использование PK дает индексу что-то еще, с чем можно работать? Собираюсь в эти выходные протестировать на большом столе и посмотреть. - person David Storfer; 08.10.2011
comment
@DavidStorfer, вы не можете этого сделать, потому что у вас может быть конфликт между идентификаторами двух разных таблиц. - person Matt Sgarlata; 30.07.2012
comment
Улучшение: ISNULL (X, CONVERT (VARCHAR (10), pk)) - person Faiz; 06.03.2014
comment
@Faiz: Улучшение в глазах смотрящего. Я предпочитаю внешний вид оригинала. - person onedaywhen; 31.03.2014
comment
@NunoG, это должен быть принятый ответ, поскольку он обеспечивает хорошее решение, соответствующее вашим требованиям, вместо простой ссылки на внешний сайт, который может исчезнуть. - person Frédéric; 26.02.2015

Можно использовать предикаты фильтра, чтобы указать, какие строки включать в индекс.

Из документация:

WHERE ‹filter_predicate› Создает отфильтрованный индекс, указывая, какие строки включить в индекс. Отфильтрованный индекс должен быть некластеризованным индексом таблицы. Создает отфильтрованную статистику для строк данных в отфильтрованном индексе.

Пример:

CREATE TABLE Table1 (
  NullableCol int NULL
)

CREATE UNIQUE INDEX IX_Table1 ON Table1 (NullableCol) WHERE NullableCol IS NOT NULL;
person Martin Staufcik    schedule 22.02.2021
comment
Отфильтрованные индексы были введены в SQL Server 2008. OP заявляет, что он использует 2005 (вопрос 12,5 лет назад, следовательно, номер устаревшей версии). - person SchmitzIT; 22.02.2021
comment
@SchmitzIT Спасибо за объяснение. - person Martin Staufcik; 22.02.2021

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

Однако это не означает, что концепция «уникальных столбцов, допускающих значение NULL» верна; чтобы фактически реализовать его в любой реляционной базе данных, мы просто должны иметь в виду, что такие базы данных предназначены для нормализации для правильной работы, а нормализация обычно включает добавление нескольких (не связанных с сущностями) дополнительных таблиц для установления отношений между сущностями. .

Давайте рассмотрим базовый пример, рассматривающий только один «уникальный столбец, допускающий значение NULL», его легко расширить до большего количества таких столбцов.

Предположим, у нас есть информация, представленная в виде такой таблицы:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

Мы можем сделать это, отделив uniqnull и добавив вторую таблицу, чтобы установить связь между значениями uniqnull и the_entity (вместо того, чтобы иметь uniqnull «внутри» the_entity):

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

Чтобы связать значение uniqnull со строкой в ​​the_entity, нам нужно также добавить строку в the_relation.

Для строк в the_entity не были связаны значения uniqnull (т.е. для тех, которые мы поместили бы NULL в the_entity_incorrect), мы просто не добавляем строку в the_relation.

Обратите внимание, что значения для uniqnull будут уникальными для всех the_relation, а также обратите внимание, что для каждого значения в the_entity может быть не более одного значения в the_relation, поскольку первичный и внешний ключи на нем обеспечивают это.

Затем, если значение 5 для uniqnull должно быть связано с идентификатором the_entity, равным 3, нам необходимо:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

И, если значение id равное 10 для the_entity не имеет аналога uniqnull, мы делаем только:

start transaction;
insert into the_entity (id) values (10); 
commit;

Чтобы денормализовать эту информацию и получить данные, которые могла бы содержать таблица типа the_entity_incorrect, нам необходимо:

select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

Оператор «левое внешнее соединение» гарантирует, что все строки из the_entity появятся в результате, помещая NULL в столбец uniqnull, когда в the_relation нет соответствующих столбцов.

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

person roy    schedule 24.02.2015
comment
Как уже было сказано в комментарии к принятому ответу с пятьдесят положительными голосами, MS Sql Server должен поддерживать наличие нескольких значений NULL в столбцах, индексированных как уникальные. Чтобы этого не допустить, является неспособность реализовать стандарты SQL. Null не является значением, null не равно null, это базовое правило SQL с давних пор. Итак, ваше первое предложение неверно, и большинство читателей не станут читать дальше. - person Frédéric; 26.02.2015