ORM-команды с классом Q

Курс по Django: https://stepik.org/a/183363

Начиная с этого занятия, мы с вами немного углубимся в ORM Django, чтобы вы лучше представляли себе его возможности и могли применять их на практике. Некоторое введение у нас с вами уже есть, в частности, умеем работать с записями отдельных таблиц, а именно:

  • Create – создавать записи;
  • Read – читать записи;
  • Update – менять записи;
  • Delete – удалять записи.

Набор команд из этой серии часто объединяют в аббревиатуру CRUD и им были посвящены первые занятия по ORM. Также мы уже умеем работать со связанными таблицами и знаем три вида связей:

  • one-to-one (один к одному);
  • many-to-one (многие к одному);
  • many-to-many (многие ко многим).

Вообще, ORM Django имеет очень богатый функционал и вам вряд ли понадобится переходить на уровень SQL-запросов, так как все можно сделать на уровне этого API. Кроме того, начинающим разработчикам уровень ORM позволит писать грамотные и оптимизированные запросы к БД любого типа: SQLite, MySQL, PostgreSQL, Oracle, то есть, приложение будет совершенно независимым от типа СУБД. Все это и привело к тому, что сейчас, в основном, используются различные ORM при работе с таблицами БД. Подробную информацию обо всем этом можно посмотреть в документации:

https://docs.djangoproject.com/en/4.2/#the-model-layer

В частности ссылка «методы QuerySet»:

https://docs.djangoproject.com/en/4.2/ref/models/querysets/

приводит нас на страницу с описанием различных методов, которые доступны в ORM Django. Как всегда, советую с ними подробно ознакомиться, чтобы грамотно их использовать в своих проектах. Чаще всего используются методы: filter, all, get, о которых мы уже подробно говорили. Также здесь вы найдете, так называемые, lookup’ы, которые позволяют формировать различные условия для выборки записей, и ниже представлены функции агрегации.

На этом занятии мы продолжим изучать команды ORM Django и для этого я перейду в терминал и запущу консоль:

python manage.py shell_plus --print-sql

Ранее мы с вами говорили, что если в методе filter через запятую указать несколько именованных аргументов, например, так:

Women.objects.filter(pk__in=[2,5,7,10], is_published=True)

то сформируется SQL-запрос с условием AND:

WHERE ("women_women"."is_published" AND "women_women"."id" IN (2, 5, 7, 10))

Если в условии нужно использовать логическое ИЛИ, а также НЕ, то вместо перечисления критериев отбора через запятую, следует использовать специальный класс Q. С помощью этого класса можно описывать более сложные критерии (условия), используя специальные операторы:

  • & логическое И (приоритет 2);
  • | логическое ИЛИ (приоритет 1 – самый низкий);
  • ~ логическое НЕ (приоритет 3 – самый высокий).

Давайте посмотрим, как это все работает. Вначале его нужно импортировать:

from django.db.models import Q

Теперь, смотрите, если выполнить вот такой запрос:

Women.objects.filter(pk__lt=5, cat_id=2)

то на выходе получим пустой список, т.к. все записи с id < 5 относятся к первой категории. Но сейчас мы с помощью класса Q соединим эти два условия по логическому ИЛИ:

Women.objects.filter(Q(pk__lt=5) | Q(cat_id=2))

То теперь видим записи, у которых или id<5 или cat_id=2. Кстати, предыдущий запрос тоже можно записать через класс Q следующим образом:

Women.objects.filter(Q(pk__lt=5) & Q(cat_id=2))

Ну и, наконец, если перед классом прописать тильду, то условие превратится в обратное:

Women.objects.filter(~Q(pk__lt=5) | Q(cat_id=2))

Здесь мы отбираем записи, у которых id >=5 или cat_id=2.

Разумеется, в методе filter() можно комбинировать объекты класса Q с обычными аргументами, например, так:

Women.objects.filter(Q(pk__in=[1, 2, 5]) | Q(cat_id=2), title__icontains="ра")

Тогда первые два выражения будут объединены по ИЛИ, а третье по И:

WHERE (("women_women"."id" IN (1, 2, 5) OR "women_women"."cat_id" = 2) AND "women_women"."title" LIKE '%ра%' ESCAPE '\')

Причем первые два условия объединены в круглые скобки и образуют одно единое подусловие. То есть, в методе filter() каждый аргумент, перечисленный через запятую, образует свое независимое подусловие.

Но, обратите внимание, мы не можем указывать обычные аргументы до класса Q:

Women.objects.filter(title__icontains="ра", Q(pk__in=[1, 2, 5]) | Q(cat_id=2))

Получим ошибку. То есть, мы должны такие параметры прописывать либо после класса Q, либо обертывать аргумент также в класс Q:

Women.objects.filter(Q(title__icontains="ра"), Q(pk__in=[1, 2, 5]) | Q(cat_id=2))

Тогда все отработает без проблем.

Если же нам нужно сделать то же самое, но без дополнительных круглых скобок вокруг OR, то следует все прописать через класс Q следующим образом:

Women.objects.filter(Q(pk__in=[1, 2, 5]) | Q(cat_id=2) & Q(title__icontains="ра"))

Вот так можно использовать класс Q для описания запросов с использованием операторов &, | и ~. И всегда помните о приоритетах операций: сначала выполняется НЕ, затем, И и в последнюю очередь ИЛИ.

Курс по Django: https://stepik.org/a/183363

Видео по теме