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