Как я могу иметь отдельные условия триггера и фильтра в Django UniqueConstraint?
Учитывая следующие модели:
class Customer(models.Model):
pass
class User(models.Model):
email = models.EmailFIeld(blank=True, default="")
customer = models.ForeignKey(Customer, ...)
Я хочу обеспечить выполнение следующего:
IF user has email
IF user has customer
email must be globally unique
IF user has no customer
email must be unique within the user's customer
Я попытался реализовать это с помощью двух UniqueConstraint
s:
UniqueConstraint(
name="customer_scoped_unique_email",
fields=["customer", "email"],
condition=(
Q(customer__isnull=False)
& ~Q(email=None)
),
),
UniqueConstraint(
name="unscoped_unique_email",
fields=["email"],
condition=(
Q(customer=None)
& ~Q(email=None)
),
),
Тестирование показало, что это все еще позволяет создать пользователя без клиента с email, идентичным существующему пользователю ( с клиентом). Насколько я понимаю, это происходит потому, что UniqueConstraint.condition
определяет, когда должно срабатывать ограничение уникальности и какие другие записи включаются в проверку уникальности.
Есть ли способ достичь желаемой логики в базе данных , в идеале с поддержкой Django ORM, а в идеале с UniqueConstraint
или CheckConstraint
? Это должно происходить в базе данных. Это очевидно возможно в Python, но я хочу дополнительную надежность ограничения базы данных.
Есть ли способ достичь желаемой логики в базе данных ...
Да, вы можете использовать триггеры (см. SQL в разделе Триггеры ниже).
... в идеале в Django ORM-поддерживаемым способом ...
Не в рамках Django ORM, но для PostgreSQL, вы можете использовать django-pgtrigger
для определения этого в моделях.
... и в идеале с
UniqueConstraint
илиCheckConstraint
?
Это не поддерживается на уровне базы данных, поскольку частичные индексы содержат только строки, основанные на WHERE
.
Частичные индексы
UniqueConstraint.condition имеет те же ограничения базы данных, что и Index.condition.
PostgreSQL: https://www.postgresql.org/docs/8.0/indexes-partial.html
.Частичный индекс - это индекс, построенный по подмножеству таблицы; подмножество определяется условным выражением (называемым предикатом частичного индекса). Индекс содержит записи только для тех строк таблицы, которые удовлетворяют предикату.
- SQLite:
https://www.sqlite.org/partialindex.html.
Частичный индекс - это индекс по подмножеству строк таблицы.
В индекс включаются только те строки таблицы, для которых условие WHERE имеет значение true.
- Триггер PostgreSQL:
Триггеры
Перед вставкой или обновлением в таблице user
проверьте уникальный email.
CREATE OR REPLACE FUNCTION unscoped_unique_email() RETURNS TRIGGER AS $unscoped_unique_email$
DECLARE
is_used_email bool;
BEGIN
IF NEW.email IS NOT NULL AND NEW.customer_id IS NULL THEN
SELECT TRUE INTO is_used_email FROM user WHERE email = NEW.email;
IF is_used_email IS NOT NULL THEN
RAISE EXCEPTION 'duplicate key value violates unique constraint "unscoped_unique_email"'
USING DETAIL = format('Key (email)=(%s) already exists.', NEW.email);
END IF;
END IF;
RETURN NEW;
END;
$unscoped_unique_email$ LANGUAGE plpgsql;
CREATE TRIGGER unscoped_unique_email BEFORE INSERT OR UPDATE ON user
FOR EACH ROW EXECUTE PROCEDURE unscoped_unique_email();