LINUX.ORG.RU

Database level constraint

 , , , ,


0

1

Всем привет.

Есть в БД такое понятие, как ограничения. Например есть ограничение unique_together, когда нельзя иметь в БД 2 записи с одинаковыми парами полей. Но это ограничение не всегда работает в django. Т.е. запросто с этим ограничением можно создать объекты в базе методом model.save или через queryset: Model.objects.create — чтобы этого избежать, нужно вызвать метод full_clean или validate_unique.

Это всё методы из django ORM. А есть ли какой-то способ запретить на уровне базы данных? Т.е. создать такое ограничение, которое просто не позволит сделать insert, вызвав ошибку?

Ответ на: комментарий от Infra_HDC

Ну это понятно. Проблема же в чём (в понятии django):

  1. создаём сигнал и вызываем full_clean постоянно
  2. не забываем вызывать full_clean везде, где создаются элементы.

1 — откровенно плох.
2 — приведёт к ошибкам рано или поздно.

conformist ★★★
() автор топика

На уровне базы данных можно запретить что угодно, т.к. там тьюринг-полный язык.

Legioner ★★★★★
()
Ответ на: комментарий от conformist

Это не будет работать, если 2 поля могут быть null, к сожалению.

В исходной задаче про это ничего не сказано.

AnDoR ★★★★★
()

Но это ограничение не всегда работает в django

Но оно ведь создает в БД ограничения в виде ... CONSTRAINT `name` UNIQUE (`field_1`, `field_2`) .... Что у тебя не работает?

gruy ★★★★★
()
Ответ на: комментарий от conformist

Это не будет работать, если 2 поля могут быть null, к сожалению.

Это фича.

x3al ★★★★★
()
Ответ на: комментарий от gruy

Вот, что я хочу сделать:

class Holiday(models.Model):
    name = models.CharField(max_length=100)
    date = models.DateField()
    country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True)
    person = models.ForeignKey(Person, on_delete=models.CASCADE, null=True)

    class Meta:
        ordering = ('country__name', 'date')
        constraints = [
            UniqueConstraint(
                fields=['date', 'country', 'person'],
                name='unique_with_person'
            ),
            UniqueConstraint(
                fields=['date', 'country'],
                condition=models.Q(person__isnull=True),
                name='unique_without_person'
            ),
            UniqueConstraint(
                fields=['date', 'person'],
                condition=models.Q(country__isnull=True),
                name='unique_without_country'
            ),
        ]

Что нужно: если указаны все 3 — они должны быть уникальны. Если дата и страна без личности — нельзя создать 2 на один день. Так же нельзя создать на один день без страны (без страны — это типа любая страна, потому там null=True).

Это не работает даже с full_clean. Я без проблем могу создавать объекты в базе кроме одной связки — когда указаны все 3 параметра. Все 3 сразу выдают ошибку.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

Это не работает даже с full_clean.

вообще-то full_clean и не проверяет ограничения: https://docs.djangoproject.com/en/3.0/ref/models/constraints/

ЗЫ. А зачем поле name в этой модели? Почему нельзя обращаться к соответствующему полю в Person? Вы ведь знаете про нормальные формы БД?

zad1ra
()
Ответ на: комментарий от zad1ra

Потому что name — это название праздника. validate_unique так же не реагирует.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

могу создавать объекты в базе кроме одной связки — когда указаны все 3 параметра.

имеется ввиду первое ограничение для ['date', 'country', 'person']?

И какая именно ошибка вылазит? можно привести сюда?

zad1ra
()
Ответ на: комментарий от gruy
--
-- Alter field country on holiday
--
ALTER TABLE `core_holiday` DROP FOREIGN KEY `core_holiday_country_id_b59c19a3_fk_core_country_id`;
ALTER TABLE `core_holiday` ADD CONSTRAINT `core_holiday_country_id_b59c19a3_fk_core_country_id` FOREIGN KEY (`country_id`) REFERENCES `core_country` (`id`);
--
-- Alter field person on holiday
--
ALTER TABLE `core_holiday` DROP FOREIGN KEY `core_holiday_person_id_1ec89b58_fk_core_person_id`;
ALTER TABLE `core_holiday` ADD CONSTRAINT `core_holiday_person_id_1ec89b58_fk_core_person_id` FOREIGN KEY (`person_id`) REFERENCES `core_person` (`id`);
--
-- Alter field country on personholiday
--
ALTER TABLE `core_personholiday` DROP FOREIGN KEY `core_personholiday_country_id_cd3fd6dd_fk_core_country_id`;
ALTER TABLE `core_personholiday` ADD CONSTRAINT `core_personholiday_country_id_cd3fd6dd_fk_core_country_id` FOREIGN KEY (`country_id`) REFERENCES `core_country` (`id`);
--
-- Create constraint unique_with_person on model holiday
--
ALTER TABLE `core_holiday` ADD CONSTRAINT `unique_with_person` UNIQUE (`date`, `country_id`, `person_id`);
--
-- Create constraint unique_without_person on model holiday
--
--
-- Create constraint unique_without_country on model holiday
--

Очень интересно, но sqlmigrate показывает, что создаются 2 unique constraints, но они пустые, нет никаких команд. А для третьего варианта, где все уникальные вместе — создаётся.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

привёл пример

без страны — это типа любая страна, потому там null=True

Ну и? Почему «любая страна» должна быть null, а не ‘any’ или просто пустая стока? Ещё раз, null тебе не нужен.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Допустим, объект «Все страны» я могу создать и использовать его везде, но что делать с Person? я же не могу использовать какого-то человека-заглушку везде.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

использовать какого-то человека-заглушку везде

Не то что можешь, а обязан сделать такого John Doe. В т.ч. чтобы он автоматом загружался как соответствующий объект. Ты сейчас-то это как решаешь? Костыли городишь вроде if user == None:? Сам себе проблему устроил, теперь страдаешь.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 3)
Ответ на: комментарий от no-such-file

Я решаю эту проблему просто — если есть человек, ок, у него праздник. Для каждой же страны существуют государственные праздники, в них не нужны пользователи, это по умолчанию. Пользователи так же могут быть в разных национальных группах. Хранить это значение как null — вполне адекватная идея на мой взгляд.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

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

Ты какую-то херню городишь. Для праздников сделай отдельную таблицу. Cо странами и пользователями связывай через промежуточные таблицы. Тогда на каждую промежуточную таблицу ты сможешь навесить unique, а также требовать чтобы праздник был связан со страной (напомню что «любая страна» должна быть записью в таблице стран, а не просто null).

PS: просто забудь что есть null. Переделай так чтобы работало без такой фичи и всё будет ровно и шелковисто. Третий раз повторю, все твои проблемы из-за кривого дизайна на null.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Так у меня и так 3 таблицы. Праздник, Страна, Пользователь. Если не указан пользователь, я хочу, чтобы дата и страна были уникальной парой. Если страна не указана, я хочу чтобы пользователь и дата были уникальной парой. Это достаточно простое требование, на самом деле, не нужно ещё дополнительных промежуточных таблиц.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

Так у меня и так 3 таблицы. Праздник, Страна, Пользователь

Нет у тебя 2 таблицы и 1 для связи праздников с этими двумя. Я так понимаю что праздники там могут дублироваться (для разных комбинаций пользователь-страна) и в этом-то и проблема. А всё потому что ты не осилил в нормализацию и пытаешься затыкать дыру через null и прочую магию. Ну, желаю успехов по навешиванию костылей триггеров и чеков.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от no-such-file

Ну причём здесь нормализация? Куда дальше нормализировать? Праздник — одна из сущностей, вообще не главная и сама в себе хранит название и дату. А так же 2 связи, к чему относится конкретно этот праздник. Вот и всё, проще некуда.

conformist ★★★
() автор топика
Ответ на: комментарий от conformist

Куда дальше нормализировать?

Я вроде бы уже объяснил как и куда. Тебе нужно решение, или доказать свою правоту? Ты как-то определись.

А так же 2 связи, к чему относится конкретно этот праздник

Вот только связей нужно не две, а произвольное количество. У тебя же не одна страна и не один пользователь.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Тебе нужно решение, или доказать свою правоту? Ты как-то определись.

решение, конечно. Но на данном этапе я не вижу смысла добавлять промежуточную таблицу, чтобы выстроить уникальные ограничения. Текущая комбинация в принципе решает задачу, программно можно легко добиться желаемого, но вопрос был про ограничения на уровне БД. Т.е. если что-то из трёх null — не берём в рассчёт и следим за уникальностью оставшейся пары.

conformist ★★★
() автор топика
Последнее исправление: conformist (всего исправлений: 1)
Ответ на: комментарий от conformist

вопрос был про ограничения на уровне БД

На уровне БД есть 2 варианта. Либо ты делаешь такую декомпозицию, в которой твои хотелки можно выразить в форме отношений между данными. Либо «решаешь программно», т.е. триггеры и т.п. алгоритмические проверки. В некоторых СУБД можно добавить произвольные проверки на данные, например, а не только по ключам.

если что-то из трёх null — не берём в рассчёт и следим за уникальностью оставшейся пары

Это алгоритм, а не формальное описание отношений. Должно быть без «если».

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Согласен с этим. Почему-то я посчитал, что реализация UniqueConstraint с дополнительным подзапросом как раз решает поставленную задачу, на деле же nullable атрибуты просто игнорируются.

Спасибо большое за советы и дискуссию, полагаю, на данном этапе тему можно отметить решённой.

conformist ★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.