Django ORM, методы возвращающие QuerySet
Методы, возвращающие QuerySet
Эти методы не выполняют запрос сразу, а возвращают новый QuerySet с добавленным условием. Поэтому их можно соединять в цепочки в любом порядке — Django соберёт из цепочки один SQL-запрос.
Book.objects.filter(pages__gt=100).exclude(author__name="Чехов").order_by("-published")[:10]
Фильтрация: filter() и exclude()
Book.objects.filter(author__name="Толстой", pages__gt=500) # AND
Book.objects.filter(author__name="Толстой").filter(pages__gt=500) # тоже AND
Book.objects.exclude(pages__lt=100) # NOT
Для OR используют объекты Q:
from django.db.models import Q
Book.objects.filter(Q(author__name="Толстой") | Q(author__name="Чехов"))
Book.objects.filter(Q(pages__gt=500) & ~Q(author__name="Толстой")) # ~ — НЕ
Сортировка: order_by() и reverse()
Book.objects.order_by("title") # по возрастанию
Book.objects.order_by("-pages", "title") # по убыванию страниц, затем по названию
Book.objects.order_by("author__name") # по полю связанной модели
Book.objects.order_by("?") # случайно (медленно на больших таблицах)
Book.objects.order_by("title").reverse() # обратный порядок
order_by() без аргументов отменяет сортировку из Meta.ordering.
Уникальность: distinct()
Author.objects.filter(books__pages__gt=500).distinct()
Без distinct() автор с тремя толстыми книгами попал бы в результат трижды (из-за JOIN).
Словари и кортежи: values() и values_list()
Когда объекты моделей не нужны, а нужны только данные:
Book.objects.values("title", "pages")
# <QuerySet [{'title': 'Война и мир', 'pages': 1300}, ...]>
Book.objects.values_list("title", "pages")
# <QuerySet [('Война и мир', 1300), ...]>
Book.objects.values_list("title", flat=True)
# <QuerySet ['Война и мир', 'Анна Каренина', ...]>
Book.objects.values("author__name") # можно брать поля через связи
Работает быстрее: Django не создаёт объекты моделей.
Связанные объекты: select_related() и prefetch_related()
# Плохо: 1 запрос за книгами + N запросов за авторами
for book in Book.objects.all():
print(book.title, book.author.name)
# Хорошо: один запрос с JOIN
for book in Book.objects.select_related("author"):
print(book.title, book.author.name)
| Метод | Для каких связей | Как работает |
|---|---|---|
select_related |
ForeignKey, OneToOne (прямая сторона) | SQL JOIN, один запрос |
prefetch_related |
ManyToMany, обратные связи | Отдельный запрос + склейка в Python |
Post.objects.select_related("author").prefetch_related("tags", "comments")
Вычисляемые поля: annotate()
annotate добавляет к каждому объекту поле, вычисленное в базе:
from django.db.models import Count, Avg
authors = Author.objects.annotate(books_count=Count("books"))
for a in authors:
print(a.name, a.books_count)
Author.objects.annotate(avg_pages=Avg("books__pages")).filter(avg_pages__gt=300)
По аннотированному полю можно фильтровать и сортировать.
Частичная загрузка: only() и defer()
Book.objects.only("title") # загрузить только title (и id)
Book.objects.defer("description") # загрузить всё, кроме тяжёлого поля
Обращение к незагруженному полю вызовет дополнительный запрос — используйте осознанно.
Даты: dates() и datetimes()
Post.objects.dates("publish", "month")
# список первых дней месяцев, в которые есть посты — удобно для архива блога
Объединение запросов
qs1 = Book.objects.filter(pages__lt=100)
qs2 = Book.objects.filter(pages__gt=1000)
qs1.union(qs2) # UNION
qs1.intersection(qs2) # INTERSECT
qs1.difference(qs2) # EXCEPT
qs1 | qs2 # OR условий
Прочие полезные
Book.objects.none() # пустой QuerySet (без запроса к БД)
Book.objects.all() # копия QuerySet
Book.objects.filter(...).select_for_update() # блокировка строк в транзакции
Book.objects.using("replica") # другая база данных
Срезы
Book.objects.all()[:5] # LIMIT 5
Book.objects.all()[5:10] # OFFSET 5 LIMIT 5
Отрицательные индексы не поддерживаются. Срез тоже возвращает QuerySet (если без шага).
Итог
- Эти методы ленивые и комбинируются цепочкой в один запрос.
filter/exclude+Qдля сложных условий,order_byдля сортировки.values/values_list— данные без объектов моделей.select_related(JOIN) иprefetch_related(отдельный запрос) решают проблему N+1.annotateдобавляет вычисляемые поля.
Практика решено 0 из 7
Какие из этих методов возвращают новый QuerySet (их можно продолжать цепочкой)?
Получите список только названий всех книг, отсортированных по алфавиту,
в виде плоского списка строк (не кортежей). Результат — в titles.
titles = Book.objects.order_by("title").values_list("title", flat=True)
Что выбрать, чтобы при выводе списка книг с именами авторов (ForeignKey) не делать запрос на каждого автора?
Получите авторов с дополнительным полем books_count — числом их книг (обратная связь books),
отсортированных по убыванию этого числа. Результат — в authors.
from django.db.models import Count
authors = Author.objects.annotate(books_count=Count("books")).order_by("-books_count")
Что вернёт Book.objects.values("title", "pages")?
Какой метод QuerySet убирает повторяющиеся строки из результата (SQL DISTINCT)?
Как будет отсортирован результат Book.objects.order_by("-pages", "title")? Расставьте книги.
- «Бесы», 860 стр.
- «Война и мир», 1300 стр.
- «Анна Каренина», 860 стр.
- «Муму», 40 стр.
Войдите, чтобы отмечать пройденные уроки.