SQL Server 2008 и выше
Просто отфильтруйте уникальный индекс:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
В более низких версиях материализованное представление все еще не требуется
Для SQL Server 2005 и более ранних версий вы можете сделать это без представления. Я только что добавил уникальное ограничение, как вы просите, к одной из моих таблиц. Учитывая, что мне нужна уникальность в столбце SamAccountName
, но я хочу разрешить несколько значений NULL, я использовал материализованный столбец, а не материализованное представление:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
Вы просто должны поместить что-то в вычисляемый столбец, который будет гарантированно уникальным по всей таблице, когда фактический желаемый уникальный столбец равен NULL. В данном случае PartyID
это столбец идентификаторов, и числовое значение никогда не будет совпадать ни с одним SamAccountName
, поэтому для меня это сработало. Вы можете попробовать свой собственный метод - убедитесь, что вы понимаете область своих данных, чтобы не было возможности пересечения с реальными данными. Это может быть так же просто, как добавить символ дифференцирования, например так:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Даже если когда PartyID
-нибудь станет нечисловым и может совпадать с SamAccountName
, теперь это не будет иметь значения.
Обратите внимание, что наличие индекса, включающего вычисляемый столбец, неявно приводит к тому, что каждый результат выражения сохраняется на диск вместе с другими данными в таблице, что ДОЛЖНО занимать дополнительное место на диске.
Обратите внимание, что если вы не хотите индексировать, вы все равно можете сохранить ЦП, предварительно рассчитав выражение на диске, добавив ключевое слово PERSISTED
в конец определения выражения столбца.
В SQL Server 2008 и выше обязательно используйте фильтрованное решение, если это возможно!
полемика
Обратите внимание, что некоторые специалисты по базам данных будут рассматривать это как случай «суррогатных NULL», которые определенно имеют проблемы (в основном из-за проблем, связанных с попыткой определить, является ли что-то реальным или суррогатным значением для отсутствующих данных ; также могут быть проблемы с числом ненулевых суррогатных значений, умножающихся как сумасшедшие).
Тем не менее, я считаю, что этот случай отличается. Вычисляемый столбец, который я добавляю, никогда не будет использоваться для определения чего-либо. Он не имеет смысла сам по себе и не кодирует информацию, которая не найдена отдельно в других, правильно определенных столбцах. Он никогда не должен быть выбран или использован.
Итак, моя история такова, что это не суррогатный NULL, и я придерживаюсь его! Поскольку на самом деле мы не хотим, чтобы значение, отличное от UNIQUE
NULL, использовалось не для того, чтобы обмануть индекс, чтобы игнорировать NULL, в нашем сценарии использования нет проблем, возникающих при обычном создании суррогатного NULL.
Несмотря на все сказанное, у меня нет проблем с использованием индексированного представления вместо этого, но это вызывает некоторые проблемы, такие как требование использования SCHEMABINDING
. Получайте удовольствие, добавляя новый столбец в вашу базовую таблицу (вам как минимум придется отбросить индекс, а затем отбросить представление или изменить представление, чтобы оно не было привязано к схеме). См. Полный (длинный) список требований для создания индексированного представления в SQL Server (2005) (также более поздние версии), (2000) .
Обновить
Если ваш столбец числовой, может возникнуть проблема обеспечения того, чтобы использование уникального ограничения Coalesce
не приводило к коллизиям. В этом случае есть несколько вариантов. Можно было бы использовать отрицательное число, чтобы поместить «суррогатные значения NULL» только в отрицательный диапазон, а «реальные значения» только в положительный диапазон. В качестве альтернативы можно использовать следующий шаблон. В таблице Issue
(где IssueID
находится PRIMARY KEY
) может быть или не быть TicketID
, но если она есть, она должна быть уникальной.
ALTER TABLE dbo.Issue ADD TicketUnique
AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
UNIQUE (TicketID, TicketUnique);
Если IssueID 1 имеет тикет 123, UNIQUE
ограничение будет на значениях (123, NULL). Если IssueID 2 не имеет билета, он будет включен (NULL, 2). Некоторые рассуждения покажут, что это ограничение не может быть продублировано для какой-либо строки в таблице, и, тем не менее, допускает несколько значений NULL.