Django ORM, поисковые выражения и агрегатные функции
Поисковые выражения (lookups)
Lookup — это суффикс после двойного подчёркивания, который задаёт оператор сравнения: поле__lookup=значение. Без суффикса подразумевается __exact.
Сравнение
Book.objects.filter(pages__exact=100) # = 100 (то же, что pages=100)
Book.objects.filter(pages__gt=100) # > 100
Book.objects.filter(pages__gte=100) # >= 100
Book.objects.filter(pages__lt=100) # < 100
Book.objects.filter(pages__lte=100) # <= 100
Book.objects.filter(pages__range=(100, 300)) # BETWEEN 100 AND 300
Book.objects.filter(id__in=[1, 3, 5]) # IN (1, 3, 5)
Book.objects.filter(isbn__isnull=True) # IS NULL
Строки
Book.objects.filter(title__contains="Мир") # LIKE '%Мир%' (с учётом регистра)
Book.objects.filter(title__icontains="мир") # без учёта регистра
Book.objects.filter(title__startswith="Война")
Book.objects.filter(title__istartswith="война")
Book.objects.filter(title__endswith="мир")
Book.objects.filter(title__iexact="война и мир")
Book.objects.filter(title__regex=r"^(Война|Мир)")
SQLite и кириллица
В SQLite icontains и iexact не учитывают регистр только для латиницы. В PostgreSQL кириллица работает корректно — ещё одна причина перейти на него (будет урок).
Даты
Post.objects.filter(publish__year=2025)
Post.objects.filter(publish__month=3)
Post.objects.filter(publish__day=8)
Post.objects.filter(publish__week_day=2) # понедельник (1 — воскресенье)
Post.objects.filter(publish__date=date(2025, 3, 8))
Post.objects.filter(publish__year__gte=2024) # lookups комбинируются
Через связи
Book.objects.filter(author__name__startswith="Т")
Post.objects.filter(comments__author__username="anna").distinct()
Q-выражения: сложные условия
По умолчанию все условия в filter() объединяются через И. Для ИЛИ и НЕ — объекты Q:
from django.db.models import Q
Book.objects.filter(Q(pages__lt=100) | Q(pages__gt=1000)) # ИЛИ
Book.objects.filter(~Q(author__name="Толстой")) # НЕ
Book.objects.filter(
Q(title__icontains="война") | Q(title__icontains="мир"),
published__year__gte=1850, # И (обычный аргумент — после Q)
)
Q удобно собирать динамически — например, для поиска по нескольким полям:
query = Q()
for word in "война мир".split():
query |= Q(title__icontains=word)
Book.objects.filter(query)
F-выражения: ссылка на поле
F("поле") — значение столбца в базе данных. Позволяет сравнивать поля между собой и делать атомарные обновления:
from django.db.models import F
Book.objects.filter(pages__gt=F("chapters") * 20) # сравнение двух полей
Post.objects.filter(pk=1).update(views=F("views") + 1) # атомарный счётчик
Book.objects.annotate(pages_per_chapter=F("pages") / F("chapters"))
Book.objects.order_by(F("published").desc(nulls_last=True))
Агрегатные функции
Count, Sum, Avg, Min, Max (из django.db.models):
from django.db.models import Avg, Count, Max, Min, Sum
# Итог по всему набору
Book.objects.aggregate(Avg("pages"), Max("pages"))
# Значение для каждого объекта
Author.objects.annotate(total=Count("books"), avg=Avg("books__pages"))
Группировка: values() + annotate()
Если поставить values() перед annotate(), Django сгруппирует результат по указанным полям:
Book.objects.values("author__name").annotate(total=Count("id")).order_by("-total")
# [{'author__name': 'Толстой', 'total': 12}, {'author__name': 'Чехов', 'total': 9}, ...]
SELECT author.name, COUNT(book.id) AS total
FROM book JOIN author ON ...
GROUP BY author.name
ORDER BY total DESC;
Условные агрегаты
Author.objects.annotate(
total=Count("books"),
big=Count("books", filter=Q(books__pages__gt=300)),
)
Фильтрация по агрегату
Author.objects.annotate(total=Count("books")).filter(total__gte=5) # HAVING COUNT >= 5
Порядок filter и annotate важен
filter() до annotate() ограничивает строки, которые попадут в подсчёт. filter() после — фильтрует по уже посчитанному значению.
Другие полезные выражения
from django.db.models import Case, Value, When
from django.db.models.functions import Coalesce, Concat, Length, Lower
Book.objects.annotate(title_len=Length("title"))
Author.objects.annotate(full=Concat("first_name", Value(" "), "last_name"))
Book.objects.annotate(
size=Case(
When(pages__lt=100, then=Value("короткая")),
When(pages__lt=500, then=Value("средняя")),
default=Value("длинная"),
)
)
Итог
- Lookups (
__gt,__icontains,__in,__range,__isnull,__year…) задают условия. Q— для ИЛИ/НЕ и динамических условий,F— ссылка на значение поля в базе.aggregate— итог по набору,annotate— значение для каждого объекта.values()передannotate()=GROUP BY;filter=Q(...)внутри агрегата — условный подсчёт.
Практика решено 0 из 7
Найдите книги, в названии которых встречается слово мир без учёта регистра. Результат — в books.
books = Book.objects.filter(title__icontains="мир")
Какой lookup найдёт книги, у которых поле isbn не заполнено (NULL)?
Найдите посты, опубликованные в 2025 году (поле publish), у которых в заголовке есть Django
или которые имеют больше 1000 просмотров (views). Используйте Q. Результат — в posts.
from django.db.models import Q
posts = Post.objects.filter(Q(title__icontains="Django") | Q(views__gt=1000), publish__year=2025)
Что делает Book.objects.filter(pages__gt=F("chapters") * 20)?
Для каждого автора посчитайте число его книг, у которых больше 300 страниц,
используя условный агрегат (filter=Q(...) внутри Count). Поле назовите big_books,
обратная связь — books. Результат — в authors.
from django.db.models import Count, Q
authors = Author.objects.annotate(big_books=Count("books", filter=Q(books__pages__gt=300)))
Какие lookups существуют в Django?
Что получится в результате Book.objects.values("author__name").annotate(total=Count("id"))?
Войдите, чтобы отмечать пройденные уроки.