Что такое Q-объекты в Django ORM и как их использовать для сложных запросов с условиями OR?
Что проверяют
Умеете ли вы выйти за пределы filter с ключевыми аргументами. Django по умолчанию склеивает условия через AND, и вопрос проверяет, знаете ли вы штатный способ собрать OR, отрицание и динамический набор условий, не скатываясь в сырой SQL.
Как отвечать
Q это объект-условие, который комбинируется операторами: | даёт OR, & даёт AND, ~ отрицание. Такие объекты передаются в filter, exclude, get и в фильтры агрегатов, причём позиционные Q идут строго перед именованными аргументами.
from django.db.models import Q
from functools import reduce
import operator
Vacancy.objects.filter(Q(city="Москва") | Q(is_remote=True), is_active=True)
terms = ["python", "django"]
cond = reduce(operator.or_, (Q(title__icontains=t) for t in terms))
Vacancy.objects.filter(cond)Динамическая сборка и есть главная причина существования Q: набор фильтров из параметров запроса складывается в одно условие вместо цепочки вызовов. Рядом стоит назвать условную агрегацию, где Q передаётся аргументом фильтра внутрь Count или Sum, и выражения Case с When.
Полезно добавить про план запроса: условие OR по разным колонкам часто мешает планировщику использовать индексы, и на больших таблицах выгоднее собрать два узких запроса и объединить их.
Где спотыкаются
Пишут or и and вместо | и &. Встроенные операторы приводят объект к логическому значению и возвращают один из аргументов, так что часть условия просто исчезает. Про приоритет тоже помнят не все: & связывает сильнее, чем |, и скобки обязательны.
Строят OR по связанной модели и получают дубликаты строк. Соединение таблиц размножает записи, поэтому нужен distinct или переход к подзапросу через поиск по вложенному списку идентификаторов.
Ждут, что отрицание вернёт строки с пустым значением. В SQL сравнение с NULL не истинно и не ложно, поэтому такие записи в выборку не попадают; их добавляют отдельным условием с проверкой на пустоту.