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", которые определенно имеют проблемы (в основном из-за проблем, связанных с попыткой определить, является ли что-то реальным значением или * 1038). * суррогатное значение для отсутствующих данных ; также могут быть проблемы с числом ненулевых суррогатных значений, умножающихся как сумасшедшие).
Однако я считаю, что это другой случай. Вычисляемый столбец, который я добавляю, никогда не будет использоваться для определения чего-либо. Он не имеет смысла сам по себе и не кодирует информацию, которая не найдена отдельно в других правильно определенных столбцах. Никогда не следует выбирать или использовать.
Итак, моя история такова, что это не суррогатный NULL, и я придерживаюсь его! Поскольку на самом деле мы не хотим, чтобы значение, отличное от NULL, использовалось не для того, чтобы обмануть индекс UNIQUE
, чтобы игнорировать NULL, в нашем сценарии использования нет ни одной из проблем, возникающих при обычном создании суррогатного NULL.
Все это говорит о том, что у меня нет проблем с использованием вместо этого индексированного представления, но оно вызывает некоторые проблемы, такие как требование использования SCHEMABINDING
. Получайте удовольствие, добавляя новый столбец в вашу базовую таблицу (вам как минимум придется отбросить индекс, а затем отбросить представление или изменить представление, чтобы оно не было привязано к схеме). См. Полный (длинный) список требований для создания индексированного представления в SQL Server (2005) (также более поздние версии), (2000) .
Update
Если ваш столбец числовой, может возникнуть проблема обеспечения того, чтобы уникальное ограничение, использующее 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). Некоторые думают, что это ограничение не может быть продублировано ни для одной строки в таблице, и все же допускает наличие нескольких пустых значений.