Field value must be unique unless it is NULL
Here is an alternative way to do it with a constraint. In order to enforce this constraint you'll need a function that counts the number of occurrences of the field value. In your constraint, simply make sure this maximum is 1.
Constraint:
field is null or dbo.fn_count_maximum_of_field(field) < 2
EDIT I can't remember right now -- and can't check it either -- whether the constraint check is done before the insert/update or after. I think after with the insert/update being rolled back on failure. If it turns out I'm wrong, the 2 above should be a 1.
Table function returns an int and uses the following select to derive it
declare @retVal int
select @retVal = max(occurrences)
from (
select field, count(*) as occurrences
from dbo.tbl
where field = @field
group by field
) tmp
This should be reasonably fast if your column as a (non-unique) index on it.
Null value isn't unique
Postgres documentation claims that this behaviour is compliant with the SQL standard:
In general, a unique constraint is violated when there are two or more
rows in the table where the values of all of the columns included in
the constraint are equal. However, two null values are not considered
equal in this comparison. That means even in the presence of a unique
constraint it is possible to store duplicate rows that contain a null
value in at least one of the constrained columns. This behavior
conforms to the SQL standard[.]
One possibility is to rethink your schema (to be honest, a uniqueness constraint on name+address+college
doesn't make a whole lots of sense in your example).
Set a unique constraint only when a field is null
MySQL supports functional key parts since 8.0.13.
If your version is sufficiently recent you can define your index as:
UNIQUE(`user_id`, `test_id`, (IFNULL(`completed_date`, -1)))
(Demo on dbfiddle.uk)
Note that the above index will also prevent duplciate dates for completed executions. If those should be valid then a slightly modified index would work:
UNIQUE(`user_id`, `test_id`, (
CASE WHEN `completed_date` IS NOT NULL
THEN NULL
ELSE 0
END))(Demo on dbfiddle.uk)
Although then it starts to feel a bit dirty ;)
If you have at least version 5.7 you can use a (virtual) generated column as workaround:
CREATE TABLE `executed_tests` (
`id` INTEGER AUTO_INCREMENT NOT NULL,
`user_id` INTEGER NOT NULL,
`test_id` INTEGER NOT NULL,
`start_date` DATE NOT NULL,
`completed_date` DATE,
`_helper` CHAR(11) AS (IFNULL(`completed_date`, -1)),
PRIMARY KEY (`id`),
UNIQUE(`user_id`, `test_id`, `_helper`)
);(Demo on dbfiddle.uk)
If you are stuck on 5.6 then a combination of a regular (non-virtual) column and slightly modified
INSERT
statements would work:CREATE TABLE `executed_tests` (
`id` INTEGER AUTO_INCREMENT NOT NULL,
`user_id` INTEGER NOT NULL,
`test_id` INTEGER NOT NULL,
`start_date` DATE NOT NULL,
`completed_date` DATE,
`is_open` BOOLEAN,
PRIMARY KEY (`id`),
UNIQUE(`user_id`, `test_id`, `is_open`)
);In this case you would set
is_open
totrue
for incomplete executions and toNULL
after completion, making use of the fact that twoNULL
s are treated as not equal.(Demo on dbfiddle.uk)
Unique constraint that allows empty values in MySQL
Yes, you can do this. See the MySQL reference (version 5.5).
A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. For all engines, a UNIQUE index allows multiple NULL values for columns that can contain NULL.
Unique fields that allow nulls in Django
Django has not considered NULL
to be equal to NULL
for the purpose of uniqueness checks since ticket #9039 was fixed, see:
http://code.djangoproject.com/ticket/9039
The issue here is that the normalized "blank" value for a form CharField
is an empty string, not None
. So if you leave the field blank, you get an empty string, not NULL
, stored in the DB. Empty strings are equal to empty strings for uniqueness checks, under both Django and database rules.
You can force the admin interface to store NULL
for an empty string by providing your own customized model form for Foo
with a clean_bar
method that turns the empty string into None
:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
def clean_bar(self):
return self.cleaned_data['bar'] or None
class FooAdmin(admin.ModelAdmin):
form = FooForm
How do I create a unique constraint that also allows nulls?
SQL Server 2008 +
You can create a unique index that accept multiple NULLs with a WHERE
clause. See the answer below.
Prior to SQL Server 2008
You cannot create a UNIQUE constraint and allow NULLs. You need set a default value of NEWID().
Update the existing values to NEWID() where NULL before creating the UNIQUE constraint.
MySQL: Avoid having to set a NULL value on an UNIQUE field before updating it
Is there a way to avoid setting this NULL?
Not in MySQL. The SQL Standard defines the feature of deferrable constraints that address this specific issue. Unfortunately, MySQL does not implement this section of SQL Specification. Nulls are your only option here.
Now, when a contraint is marked as deferrable (as in PostgreSQL or Oracle), its validation can be deferred to the end of each SQL statement execution, or even to the end of the whole transaction; that is, its full validation happens only at the time of the commit
, after all updates have been done and all values would be good again.
As you see, unless you have the option to migrate to PostgreSQL or Oracle (highly unlikely) you are stuck with using nulls.
Related Topics
Case and Coalesce Short-Circuit Evaluation Works with Sequences in Pl/SQL But Not in SQL
How to Retrieve The Identities of Rows That Were Inserted Through Insert...Select
Writing Data Back to SQL from Excel Sheet
When Should I Nest Pl/SQL Begin...End Blocks
Running a SQLite3 Script from Command Line
How to Best Handle the Storage of Historical Data
Query to Find All Fk Constraints and Their Delete Rules (Sql Server)
Any Disadvantages to Bit Flags in Database Columns
Optimising a Select Query That Runs Slow on Oracle Which Runs Quickly on SQL Server
How to Get Array/Bag of Elements from Hive Group by Operator
Using Sqldf and Rpostgresql Together
Sql Query for Insert in Grails
Is Innodb Sorting Really That Slow
Sql - How to Select Words with Certain Values at The End of Word