Как создать уникальный индекс по столбцу NULL? - PullRequest
95 голосов
/ 10 октября 2008

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

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

Ответы [ 4 ]

101 голосов
/ 07 июля 2010

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

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

71 голосов
/ 10 октября 2008

Рассчитанный трюк с колоннами широко известен как «нольбастер»; мои кредитные заметки Стив Касс:

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)
)
25 голосов
/ 10 октября 2008

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

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

0 голосов
/ 24 февраля 2015

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

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

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
;

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

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

...