Как обеспечить уникальность с помощью значений NULL в PostgreSQL

У меня есть модель с ненулевым CharField и 2 x нулевыми 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)

Я хочу убедиться, что name, title и position уникальны вместе, и поэтому использую UniqueConstraint:

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

Однако, если title является None, то это ограничение не работает.

Если разобраться, то это происходит потому, что вы можете вставлять значения NULL в столбцы с ограничением UNIQUE, поскольку NULL - это отсутствие значения, поэтому оно никогда не равно другим значениям NULL и не считается дубликатом. Это означает, что можно вставлять строки, которые кажутся дубликатами, если одно из значений NULL.

Какой правильный способ строго обеспечить эту уникальность в Django?

UniqueConstraint применяются не на уровне Django, а непосредственно к базе данных.

PostgreSQL, например, позволяет использовать несколько Null записей для уникальных, а MSSQL - нет

Вы можете проверить это для каждого подмножества столбцов с помощью CheckConstraint, но, как вы увидите, это утомительно, поскольку требует, чтобы каждая комбинация была уникально индексирована .


Чтобы избежать даже всей этой неразберихи, вы можете следовать руководству Django для CharField как документированный

Избегайте использования null в строковых полях, таких как CharField и TextField. Если поле на основе строки имеет значение null=True, это означает, что оно имеет два возможных значения для "нет данных": NULL и пустая строка. В большинстве случаях, иметь два возможных значения для "нет данных" излишне. Django принято использовать пустую строку, а не NULL. Одно исключение это когда для поля CharField установлены значения unique=True и blank=True. В этой ситуации, null=True требуется, чтобы избежать нарушений ограничения уникальности при сохранении нескольких объектов с пустыми значениями.

Пустая строка проверяется на уникальность

Вы можете выбрать значения, которые никогда не встречаются для title и position и использовать индекс следующим образом:

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

Это заменит значения NULL на что-то другое, так что дубликаты будут предотвращены.

Вернуться на верх