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

101

Я использую 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)

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

Nuno G
источник
16
нет шансов использовать sql 2008? вы можете создать отфильтрованный индекс, используя «где»
Simon_Weaver
3
Вы не имели в виду уникальность, допускающую NULL , вы, кажется, имели в виду уникальную, но включали несколько NULL . В противном случае NULL индексируется, как и любое другое значение, и ограничение уникальности работает должным образом - только не в соответствии со стандартами SQL, как @pst упоминается в комментарии ниже.
Suncat2000

Ответы:

26

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

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

скажи что
источник
2
Похоже, что содержание предоставленной вами ссылки было фактически (частично) скопировано без указания авторства ссылки: decipherinfosys.wordpress.com/2007/11/30/…
Tom Juergens
77
Я не согласен с тем, что это «нарушает цель уникальности» - NULL - это особое значение в SQL (во многих отношениях похожее на NaN) и требует соответствующей обработки. На самом деле в SQL Server не соблюдаются различные спецификации SQL: вот ссылка для запроса «правильной реализации» того, чего она стоит: connect.microsoft.com/SQLServer/feedback/details/299229/… .
5
для справки в 2008 г. вы можете сделать CREATE UNIQUE INDEX foo ON dbo.bar (key) WHERE key IS NOT NULL;
niico
2
Я также не согласен с тем, что «нарушает цель уникальности», NULL не равно NULL, поэтому вы должны иметь возможность создать уникальный индекс для столбца, допускающего значение NULL, и вставить несколько значений NULL.
Wodzu
105

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

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

Фил Хазелден
источник
84
create unique index UIX on MyTable (Column1) where Column1 is not null
Йорн Скоу-Роде,
1
Примечание: в настоящее время SQL Server Management Studio, похоже, не знает, как создавать такие индексы, поэтому, если вы позже измените таблицу, она
запутается
3
Похоже, что Microsoft обновила SSMS для поддержки этого. У меня SSMS 10.50.1617, и в диалоговом окне «Свойства индекса» вы можете выбрать страницу «Фильтр», чтобы изменить фильтр. например, «([Column1] IS NOT NULL)»
Фил Хазелден,
5
Разрешение нескольких нулей в индексе и фильтрация нулей из индекса - это разные вещи. Фильтрация индекса фактически исключает записи из индекса, тогда как другие решения преобразуют нуль в полезное уникальное значение. Знайте разницу.
Suncat2000
Если вы используете хранимые процедуры в таблице с подобным отфильтрованным индексом, убедитесь, что ANSI_NULLSэто так ON, иначе вы получите ошибку при попытке вставить данные.
Арне
71

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

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)
)
один день, когда
источник
Это похоже на крутой трюк. Как ни странно, поиск nullbuster не вызывает лишних вопросов. Мне интересно, будет ли это полезно и для ускорения поиска - вместо вычисляемого столбца только 1 и 0 для нуля или нет, если использование PK дает индексу что-то еще, с чем можно работать? Собираюсь в эти выходные протестировать на большом столе и посмотреть.
Дэвид Сторфер
@DavidStorfer, вы не можете этого сделать, потому что может возникнуть конфликт между идентификаторами двух разных таблиц.
user393274
Улучшение: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz
5
@Faiz: Улучшение в глазах смотрящего. Я предпочитаю внешний вид оригинала.
однажды, когда
@NunoG, это должен быть принятый ответ, поскольку он обеспечивает хорошее решение, соответствующее вашим требованиям, вместо простой ссылки на внешний сайт, который может исчезнуть.
Frédéric
-3

Строго говоря, уникальный столбец (или набор столбцов), допускающий значение 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 нет соответствующих столбцов.

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

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