Django-Admin.ru
Django · ORM и модели★ Руководство

Django ORM: шпаргалка по QuerySet с примерами

Что такое QuerySet в Django и как с ним работать: filter, exclude, get, Q и F-выражения, update, aggregate, annotate, транзакции и типичные ошибки.

Редакция Django-Admin.ru
· обновлено · 7 мин чтения

Содержание · 16 разделов
  1. Что такое QuerySet и когда выполняется запрос
  2. Выборка: all, filter, exclude, get
  3. Операторы поиска (lookups)
  4. Сложные условия: Q-объекты
  5. Сортировка, срезы, уникальность
  6. Только нужные поля: values, values_list, only
  7. Создание записей
  8. Обновление и удаление
  9. F-выражения: операции на стороне базы
  10. Агрегация и аннотации
  11. Связанные объекты без N+1
  12. Проверка существования и подсчёт
  13. Транзакции
  14. Сырой SQL — когда ORM не хватает
  15. Частые ошибки
  16. Частые вопросы
  17. Как получить последнюю запись?
  18. Как объединить два QuerySet одной модели?
  19. Как превратить QuerySet в JSON?
  20. Как подставить значение из другой таблицы подзапросом?
  21. Можно ли вызывать ORM в асинхронном коде?

Django ORM позволяет работать с базой данных через Python-объекты вместо SQL. Центральное понятие — QuerySet: ленивое описание запроса, которое можно уточнять цепочкой методов и которое выполняется только тогда, когда вам действительно нужны данные.

Один и тот же код ORM работает с SQLite, PostgreSQL, MySQL и другими СУБД: база выбирается в настройке DATABASES, а не в запросах. Как её настроить и когда хватает SQLite — в статье о базах данных в Django, подключение MySQL и MariaDB — в отдельной инструкции по MySQL.

Шпаргалка собрана по частым задачам. Все примеры используют две модели (как описывать поля и связи, читайте в статье «Модели в Django»):

from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    country = models.CharField(max_length=2, blank=True)


class Book(models.Model):
    title = models.CharField(max_length=300)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
    published = models.DateField()
    rating = models.DecimalField(max_digits=3, decimal_places=1, default=0)
    views = models.PositiveIntegerField(default=0)

Что такое QuerySet и когда выполняется запрос

Book.objects.filter(...) не обращается к базе — он возвращает QuerySet. Запрос выполняется при вычислении:

  • итерации: for book in qs;
  • приведении к списку или bool: list(qs), if qs:;
  • срезе с шагом и обращении по индексу: qs[0];
  • вызовах len(), repr(), а также методов get(), count(), exists(), first(), aggregate().

Результат вычисленного QuerySet кешируется: повторная итерация по тому же объекту не делает новый запрос. Новый QuerySet, полученный цепочкой (qs.filter(...)), выполнится заново.

qs = Book.objects.filter(rating__gte=4)  # запроса нет
books = list(qs)                         # SELECT ... WHERE rating >= 4
again = list(qs)                         # из кеша, без запроса

Посмотреть SQL можно через print(qs.query).

Выборка: all, filter, exclude, get

Book.objects.all()
Book.objects.filter(author__country="RU", published__year=2024)
Book.objects.exclude(rating=0)
Book.objects.get(pk=1)

get() возвращает ровно один объект. Если записей нет — исключение Book.DoesNotExist, если больше одной — Book.MultipleObjectsReturned:

try:
    book = Book.objects.get(title="Мастер и Маргарита")
except Book.DoesNotExist:
    book = None

Если отсутствие записи — нормальная ситуация, проще использовать first(): он вернёт None.

book = Book.objects.filter(title="Мастер и Маргарита").first()

Во view для ответа 404 есть готовый помощник get_object_or_404(Book, pk=pk). Как get_or_create ведёт себя при гонке и чем пользоваться в асинхронном коде — в разборе get() в Django ORM.

Операторы поиска (lookups)

Условия пишутся как поле__оператор=значение:

LookupSQL-аналогПример
exact, iexact= с учётом / без учёта регистраtitle__iexact="django"
contains, icontainsLIKE '%…%'title__icontains="orm"
startswith, endswithLIKE '…%', LIKE '%…'title__startswith="Django"
inIN (…)pk__in=[1, 2, 3]
gt, gte, lt, lte>, >=, <, <=rating__gte=4
rangeBETWEENpublished__range=(start, end)
isnullIS NULLauthor__isnull=True
year, month, dayизвлечение части датыpublished__year=2024

Через двойное подчёркивание можно идти по связям: Book.objects.filter(author__name__icontains="толстой"), и в обратную сторону — Author.objects.filter(books__rating__gte=4).distinct(). Имя обратной связи задаёт related_name у внешнего ключа; параметры on_delete, null и связь модели с самой собой разобраны в статье о ForeignKey в Django.

Сложные условия: Q-объекты

Несколько аргументов в filter() объединяются через AND. Для OR и NOT используйте Q:

from django.db.models import Q

Book.objects.filter(Q(rating__gte=4.5) | Q(views__gt=10_000))
Book.objects.filter(Q(author__country="RU") & ~Q(rating=0))

Сортировка, срезы, уникальность

Book.objects.order_by("-published", "title")  # минус — по убыванию
Book.objects.order_by("?")                       # случайный порядок (медленно на больших таблицах)
Book.objects.all()[:10]                          # LIMIT 10
Book.objects.all()[10:20]                        # LIMIT 10 OFFSET 10
Author.objects.filter(books__rating__gte=4).distinct()

Только нужные поля: values, values_list, only

Book.objects.values("id", "title")               # список словарей
Book.objects.values_list("title", flat=True)    # плоский список строк
Book.objects.only("id", "title")                # модели с отложенной загрузкой остальных полей

values_list(..., flat=True) удобен для получения списка ID или для pk__in. Не злоупотребляйте only()/defer(): обращение к неподгруженному полю вызовет дополнительный запрос на каждый объект.

Создание записей

from datetime import date

book = Book.objects.create(title="Django ORM", author=author, published=date.today())

author, created = Author.objects.get_or_create(name="Лев Толстой", defaults={"country": "RU"})

Author.objects.update_or_create(name="Лев Толстой", defaults={"country": "RU"})

Book.objects.bulk_create(
    [Book(title=f"Том {i}", author=author, published=date.today()) for i in range(1, 6)],
    batch_size=500,
)

bulk_create вставляет записи пачками, но не вызывает save() и сигналы pre_save/post_save.

Обновление и удаление

# Один UPDATE для всех подходящих строк
Book.objects.filter(rating=0).update(rating=1)

# Обновление одного объекта: сохраняем только изменённые поля
book.title = "Новое название"
book.save(update_fields=["title"])

# Удаление
Book.objects.filter(published__year__lt=2000).delete()

QuerySet.update() и delete() работают на уровне SQL: save() моделей не вызывается, а сигналы pre_save/post_save не срабатывают. (delete() при этом отправляет pre_delete/post_delete и обрабатывает каскады.) Когда выбирать save(), update() или bulk_update() и сколько запросов делает каждый способ — в статье «Как обновить записи в Django».

F-выражения: операции на стороне базы

F() ссылается на значение поля в базе. Это защищает от гонок, когда два процесса одновременно увеличивают счётчик:

from django.db.models import F

Book.objects.filter(pk=book.pk).update(views=F("views") + 1)

F также позволяет сравнивать поля между собой: Book.objects.filter(views__gt=F("rating") * 1000).

Агрегация и аннотации

aggregate() считает итог по всей выборке и возвращает словарь:

from django.db.models import Avg, Count, Max

Book.objects.aggregate(avg_rating=Avg("rating"), total=Count("id"))
# {'avg_rating': Decimal('4.2'), 'total': 120}

annotate() добавляет вычисляемое поле к каждому объекту:

authors = Author.objects.annotate(
    books_count=Count("books"),
    best_rating=Max("books__rating"),
).filter(books_count__gte=3).order_by("-books_count")

for author in authors:
    print(author.name, author.books_count, author.best_rating)

Группировка по полю — комбинация values() и annotate():

Book.objects.values("author__country").annotate(total=Count("id")).order_by("-total")

Связанные объекты без N+1

# ForeignKey и OneToOne — JOIN в одном запросе
Book.objects.select_related("author")

# Обратные связи и ManyToMany — отдельный запрос на связь
Author.objects.prefetch_related("books")

Как находить и устранять лишние запросы, разобрано в статье о проблеме N+1 в Django ORM.

Проверка существования и подсчёт

if Book.objects.filter(author=author).exists():  # SELECT ... LIMIT 1
    ...

Book.objects.filter(author=author).count()  # SELECT COUNT(*)

Не используйте len(qs) только ради количества: он загрузит все строки в память. Но если вы всё равно будете итерироваться по выборке, len() уже вычисленного QuerySet не сделает лишнего запроса.

Транзакции

По умолчанию каждый запрос выполняется в режиме autocommit. Чтобы несколько изменений применились атомарно, используйте transaction.atomic:

from django.db import transaction

with transaction.atomic():
    author = Author.objects.create(name="Новый автор")
    Book.objects.create(title="Первая книга", author=author, published=date.today())

Если внутри блока возникнет исключение, оба изменения откатятся. Для блокировки строк на время транзакции есть select_for_update().

Сырой SQL — когда ORM не хватает

Book.objects.raw("SELECT * FROM library_book WHERE rating > %s", [4])

Всегда передавайте параметры отдельным списком, а не форматированием строки, — иначе получите SQL-инъекцию.

Частые ошибки

  • get() без обработки исключений — ошибка 500 вместо 404. Используйте get_object_or_404 во view.
  • Цикл с save() вместо update() — тысячи запросов там, где хватило бы одного.
  • count() перед итерацией — лишний запрос; если данные всё равно нужны, вычислите выборку один раз.
  • Фильтрация по связям без distinct() — при обратных связях строки дублируются.
  • Обращение к связанным объектам в цикле — классический N+1; добавьте select_related/prefetch_related.

Полный справочник методов — в документации QuerySet API. Как те же приёмы применяются в админке, показано в руководстве по Django Admin.

Частые вопросы

Как получить последнюю запись?

latest("поле") возвращает объект с максимальным значением поля и бросает DoesNotExist на пустой таблице. last() берёт последний объект с учётом order_by() и на пустой выборке возвращает None.

Book.objects.latest("published")
Book.objects.order_by("published").last()

Как объединить два QuerySet одной модели?

Оператор | склеивает условия через OR и возвращает обычный QuerySet, с которым работают filter() и order_by(). union() выполняет SQL UNION: к результату можно применить срез, count(), exists(), order_by() и values(), а filter() — нет.

Book.objects.filter(rating__gte=5) | Book.objects.filter(author=author)
Book.objects.filter(rating__gte=5).values("title").union(
    Book.objects.filter(author=author).values("title")
)

Как превратить QuerySet в JSON?

Возьмите values() с нужными полями и сериализуйте список словарей. Даты и Decimal стандартный json не понимает — передайте DjangoJSONEncoder:

import json
from django.core.serializers.json import DjangoJSONEncoder

data = list(Book.objects.values("id", "title", "published", "rating"))
json.dumps(data, cls=DjangoJSONEncoder, ensure_ascii=False)
# → [{"id": 1, "title": "Война и мир", "published": "1869-01-01", "rating": "5.0"}, ...]

Для API удобнее сериализаторы Django REST Framework.

Как подставить значение из другой таблицы подзапросом?

Subquery с OuterRef добавляет к каждой строке результат вложенного запроса. Пример — название последней книги каждого автора:

from django.db.models import OuterRef, Subquery

newest = Book.objects.filter(author=OuterRef("pk")).order_by("-published")
authors = Author.objects.annotate(last_title=Subquery(newest.values("title")[:1]))

Подзапрос должен возвращать одну колонку и не больше одной строки — отсюда values("title")[:1].

Можно ли вызывать ORM в асинхронном коде?

Да. У методов, которые выполняют запрос, есть асинхронные версии с префиксом a: aget(), afirst(), acount(), aexists(), acreate(), aupdate(), adelete(). По QuerySet можно идти через async for. Транзакции (transaction.atomic) в асинхронном режиме пока не работают — такой код оборачивают в sync_to_async.

count = await Book.objects.filter(author=author).acount()
titles = [book.title async for book in Book.objects.filter(rating__gte=4)]
  • #Django ORM
  • #QuerySet
  • #filter
  • #annotate
  • #Базы данных
  • #Оптимизация
↑ ↓ выборEnter открытьEsc закрыть