In SQL Server, you have two main types of Indexes, one that allow duplications, and one that does not.
However, if you want a rule like "I want to forbid duplications, except for null values" you do not have a built in feature for that, as the unique index will treat the NULL value as a real value, thus allowing up to a single row with that NULL.
Apart from refactoring the DB schema, the solution I usually was doing has been to create a trigger to deal with that:
CREATE TRIGGER dbo.People_Unique_NonNull_Email
ON dbo.People
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
DECLARE @Count INT
SELECT @Count=COUNT(p.Id)
FROM People p
JOIN INSERTED i On i.Email = p.Email
WHERE i.Email IS NOT NULL
IF @Count > 0
BEGIN
RAISERROR ('Cannot put a duplicate email on People table', 16, 1)
ROLLBACK TRANSACTION
END
END
GO
However, Moran Benisty, my T-SQL Ninja pal, has pointed out that the SELECT and IF might bit a wee bit apart, and revised this to
CREATE TRIGGER dbo.People_Unique_NonNull_Email
ON dbo.People
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
IF EXISTS(SELECT 1
FROM People p
JOIN INSERTED i On i.Email = p.Email
WHERE i.Email IS NOT NULL
)
BEGIN
RAISERROR ('Cannot put a duplicate email on People table', 16, 1)
ROLLBACK TRANSACTION
END
END
GO
I'll leave adding the UPDATE case to the readers