In this post I’ll go over the relationship between unique constraints and NULLs in SQL Server, and how you can have multiple NULLs in a unique constraint.

What is a unique constraint?

A unique constraint is a database-level object that enforces value uniqueness across one or more columns that do not participate in the table’s primary key.

Unique constraints are vital for ensuring data integrity without you having to write additional code that manually checks if a value already exists before it’s inserted.

Quick demo

First, create a table with a unique constraint on one of the columns.

Then add a couple of records that match the requirement for the e-mail address to be unique.

At this point, the Users table looks like this:

SSMS result set listing the contents of the table:
ID	UserName	EMail
1	User1	user_one@mailprovider.com
2	User2	user_two@mailprovider.com
3	User3	NULL

SEO stuff: multiple nulls in a unique column

Now, to test the unique constraint, I try to add a record with an e-mail address that already exists.

Since the email address already exists in the Users table, this insert will fail with a unique constraint violation error.

Msg 2627, Level 14, State 1, Line 1
Violation of UNIQUE KEY constraint 'UQ_EMail'. Cannot insert duplicate key in object 'dbo.Users'. 
The duplicate key value is (user_one@mailprovider.com).
The statement has been terminated.

multiple nulls in a unique column

But what about NULLs?

Even though NULLs aren’t actually values, In SQL Server they also count towards the values that are only accepted once in a column with a unique constraint.

Case in point, this insert also fails with a unique constraint violation error:

Msg 2627, Level 14, State 1, Line 1
Violation of UNIQUE KEY constraint 'UQ_EMail'. Cannot insert duplicate key in object 'dbo.Users'. 
The duplicate key value is (<NULL>).
The statement has been terminated.

multiple nulls in a unique column

SQL Server vs ANSI SQL

While SQL Server’s unique constraints only accept one occurrence of NULL, the ANSI SQL standard states that uniqueness is only enforced for non-NULL values, and multiple NULLs are allowed.

Other RDBMS, like Oracle and MySQL, adhere to the ANSI SQL standard in this regard by ignoring NULLs in unique constraints.

Enforcing uniqueness while allowing multiple NULLs

A behavior similar to the ANSI SQL definition of unique constraints is achieved in SQL Server with the help of filtered unique indexes.

First, drop the existing unique constraint.

Next, I create a unique index on the EMail column that filters out NULLs.

Now, to test it, I try to insert a record with an e-mail address that already exists in the table.

As expected, the insert fails.

Msg 2601, Level 14, State 1, Line 1
Cannot insert duplicate key row in object 'dbo.Users' with unique index 'UIX_Users_EMail'. 
The duplicate key value is (user_one@mailprovider.com).
The statement has been terminated.

SEO: multiple nulls in a unique column

Time to test inserting a couple of NULLs aside from the already existing one.

This insert succeeds without any issues.

SSMS result set displaying the contents of the Users table:
ID	UserName	EMail
1	User1	user_one@mailprovider.com
2	User2	user_two@mailprovider.com
3	User3	NULL
5	NewUser1	NULL
6	NewUser2	NULL

multiple nulls in a unique column

Even though this isn’t really a constraint type object, since it’s an index, it does behave like a constraint by enforcing uniqueness among non-NULL values in that column.

But what if the exception should be a specific value?

Let’s say that the column doesn’t allow NULLs and, if an e-mail address isn’t provided, an empty string will be inserted instead.

In this case, I drop and re-create the table, but this time I add a default constraint on the EMail column.

Afterward I create the filtered unique index again, but I change the filtering condition so that it excludes empty strings.

And I insert some test records.

SSMS result set:
ID	UserName	EMail
1	User1	user_one@mailprovider.com
2	User2	user_two@mailprovider.com
3	User3	
4	User4	
5	User5

Attempting to insert any other value that already exists in the EMail column will still fail, while empty strings will not be taken into account.

The same applies for scenarios where multiple exceptions are needed. The only difference being that the filtering condition will need to be updated, so that it excludes those exceptions.

Note that I’ve used AND instead of NOT IN because filtered indexes do not support the latter. Brent Ozar has a great blog post on this topic.

Conclusion

If you have a use case that requires enforcing uniqueness, but requires one or more exceptions, unique filtered indexes are your best bet.
This has the added benefit of being able to use said index in queries that filter by that column, as long as the value in the query’s WHERE clause is not the same value that’s being filtered out in the index.