Home > Software engineering >  How to enforce uniqueness with NULL values in PostgreSQL
How to enforce uniqueness with NULL values in PostgreSQL

Time:09-29

I have a model with a non-nullable CharField and 2 x nullable CharField:

class MyModel(models.Model):
    name = models.CharField('Name', max_length=255, null=False)
    title = models.CharField('Title', max_length=255, blank=True)
    position = models.CharField('Position', max_length=255, blank=True)

I want to ensure that name, title, and position are unique together, and so use a UniqueConstraint:

def Meta:
    constraints = [
            models.UniqueConstraint(
                fields=['name', 'title', 'position'],
                name="unique_name_title_position"
            ),
        ]

However, if title is None then this constraint fails.

Looking into why, this is because you can insert NULL values into columns with the UNIQUE constraint because NULL is the absence of a value, so it is never equal to other NULL values and not considered a duplicate value. This means that it's possible to insert rows that appear to be duplicates if one of the values is NULL.

What's the correct way to strictly enforce this uniqueness in Django?

CodePudding user response:

UniqueConstraint are not enforced on Django level but rather directly on database.

PostgreSQL for instance allows multiple Null records for unique while MSSQL does not

You can rather check if for each subset of columns with CheckConstraint but as you will find out this is tedious as it requires each combination to be uniquely indexed.


Rather to avoid even whole this mess you can follow Django guide for CharField as documented

Avoid using null on string-based fields such as CharField and TextField. If a string-based field has null=True, that means it has two possible values for “no data”: NULL, and the empty string. In most cases, it’s redundant to have two possible values for “no data;” the Django convention is to use the empty string, not NULL. One exception is when a CharField has both unique=True and blank=True set. In this situation, null=True is required to avoid unique constraint violations when saving multiple objects with blank values.

Empty string is checked for uniqueness

CodePudding user response:

You could pick values that don't ever occur for title and position and use an index like this:

CREATE UNIQUE INDEX ON mytable (
   name,
   coalesce(title, '#impossible#'),
   coalesce(position, '#impossible#'),
);

That will replace the NULL values with something else, so that duplicates are prevented.

  • Related