Django ORM

Чтение данных и QuerySet API | Курс Django ORM урок 2.2

Чтение данных и QuerySet API | Курс Django ORM урок 2.2
Mikhail
Автор
Mikhail
Опубликовано 16.03.2026
0,0
Views 6

Цель урока

Разобраться, что такое QuerySet и почему он ленивый. Изучить методы получения данных: all(), filter(), exclude(), get(). Понять разницу между методами, возвращающими QuerySet, и методами, немедленно выполняющими запрос. Разобрать values(), values_list(), only(), defer(), инструменты контроля над тем, какие данные попадают в SELECT.

Необходимые знания

  • Урок 2.1: создание объектов
  • Базовое понимание SELECT в SQL

Что такое QuerySet

QuerySet это объект, представляющий выборку из базы данных. Он описывает что нужно получить, но не выполняет запрос сразу.

from shop.models import Product

# Это не запрос к БД - это описание запроса
queryset = Product.objects.all()

# И это тоже не запрос
filtered = Product.objects.filter(is_active=True)

# SQL выполняется только здесь, при итерации
for product in filtered:
    print(product.name)

Это называется ленивое вычисление (lazy evaluation). QuerySet хранит параметры запроса в памяти и отправляет SQL в PostgreSQL только когда результат действительно нужен.

Когда QuerySet выполняется

qs = Product.objects.filter(is_active=True)

# 1. Итерация
for p in qs: ...

# 2. Срез с шагом или конвертация в список
list(qs)
qs[0]
qs[0:10]

# 3. len()
len(qs)

# 4. bool()
if qs: ...
bool(qs)

# 5. repr() в shell Django выполняет QuerySet при выводе
qs  # в shell это напечатает результат

# 6. Явные методы: count(), exists(), first(), last()
qs.count()
qs.exists()
qs.first()

Кеширование QuerySet

После первого выполнения результаты кешируются внутри объекта QuerySet:

qs = Product.objects.filter(is_active=True)

# Первый раз SQL выполняется
for p in qs:
    print(p.name)

# Второй раз, данные из кеша, SQL не выполняется
for p in qs:
    print(p.price)

Но это кеш именно данного объекта qs. Новый QuerySet, новый запрос:

# Два разных объекта QuerySet = два запроса
Product.objects.filter(is_active=True)  # запрос 1
Product.objects.filter(is_active=True)  # запрос 2

Цепочки методов

Методы QuerySet возвращают новый QuerySet, это позволяет строить запросы цепочками:

products = (
    Product.objects
    .filter(is_active=True)
    .filter(category__slug="phones")
    .exclude(stock=0)
    .order_by("-price")
)

Каждый метод в цепочке добавляет условие к будущему SQL. Запрос выполняется один раз, когда цепочка завершена и нужно получить результат.

SELECT shop_product.*
FROM shop_product
INNER JOIN shop_category ON shop_product.category_id = shop_category.id
WHERE shop_product.is_active = true
  AND shop_category.slug = 'phones'
  AND shop_product.stock != 0
ORDER BY shop_product.price DESC;

all()

products = Product.objects.all()
SELECT "shop_product"."id", "shop_product"."name", ... FROM "shop_product"
ORDER BY "shop_product"."name" ASC;  -- из Meta.ordering

all() возвращает QuerySet со всеми объектами. filter() без предварительного all() работает так же. Но all() полезен в двух сценариях:

# 1. Передача базового QuerySet в функцию как аргумент по умолчанию
def get_products(queryset=None):
    if queryset is None:
        queryset = Product.objects.all()
    return queryset.filter(is_active=True)

# 2. Явное клонирование QuerySet перед модификацией
base_qs = Product.objects.filter(is_active=True)
phones_qs = base_qs.all().filter(category__slug="phones")  # base_qs не затронут

filter() и exclude()

filter()

filter() используется для отбора объектов по условию, добавляет WHERE в SQL. Несколько условий в одном вызове объединяются через AND.

# Один фильтр
Product.objects.filter(is_active=True)

# Несколько условий в одном filter() - это AND
Product.objects.filter(is_active=True, stock__gt=0)

# Эквивалентно цепочке filter()
Product.objects.filter(is_active=True).filter(stock__gt=0)
-- Оба варианта дают одинаковый SQL
SELECT * FROM shop_product
WHERE is_active = true AND stock > 0
ORDER BY name;

exclude()

exclude() используется когда удобнее описать что исключить, а не что включить, оборачивает условие в NOT (...).

# Все продукты кроме отмененных
Product.objects.exclude(is_active=False)

# Все заказы кроме отмененных и доставленных
Order.objects.exclude(status__in=["cancelled", "delivered"])
SELECT * FROM shop_order
WHERE NOT (status = 'cancelled' OR status = 'delivered')
ORDER BY created_at DESC;

При фильтрации через связанные модели это может давать неожиданные результаты, разберем в уроке 3.1 при изучении Q objects.


get()

get() возвращает ровно один объект. Если условию соответствует 0 или 2+ объекта, будет поднято исключение:

# Нормальная работа
product = Product.objects.get(id=1)
product = Product.objects.get(slug="iphone-15")

# DoesNotExist, объект не найден
try:
    product = Product.objects.get(slug="nonexistent")
except Product.DoesNotExist:
    print("Не найдено")

# MultipleObjectsReturned когда найдено несколько
try:
    product = Product.objects.get(is_active=True)  # их много
except Product.MultipleObjectsReturned:
    print("Найдено несколько объектов")
SELECT * FROM shop_product WHERE slug = 'iphone-15' LIMIT 2;

Django добавляет LIMIT 2, а не LIMIT 1, чтобы обнаружить случай "найдено больше одного" без загрузки всех строк.

get() немедленно выполняет SQL, это не ленивый метод.

get() vs filter().first()

# get() - исключение если нет объекта
product = Product.objects.get(slug="iphone-15")

# filter().first() - None если нет объекта
product = Product.objects.filter(slug="iphone-15").first()
-- filter().first()
SELECT * FROM shop_product WHERE slug = 'iphone-15' ORDER BY name LIMIT 1;

filter().first() безопаснее когда объект может отсутствовать и это нормально. get(), когда отсутствие объекта является ошибкой.


first() и last()

first() используйте когда объект может отсутствовать и это нормально, в отличие от get() не бросает исключение, возвращает None. last() нужен чтобы получить последний элемент: заказ пользователя, последнее событие в логе.

# Первый по Meta.ordering
Product.objects.filter(is_active=True).first()

# Последний по Meta.ordering
Order.objects.filter(user=user).last()
-- first() при ordering = ["name"]
SELECT * FROM shop_product WHERE is_active = true ORDER BY name ASC LIMIT 1;

-- last() при ordering = ["-created_at"]
SELECT * FROM shop_order WHERE user_id = 1 ORDER BY created_at ASC LIMIT 1;

last() разворачивает ordering, добавляет DESC. Если ordering не задан и не указан order_by(), результат непредсказуем.


values() - словари вместо объектов

По умолчанию QuerySet возвращает экземпляры модели. values() возвращает словари:

# Возвращает объекты Product
products = Product.objects.filter(is_active=True)
for p in products:
    print(p.name, p.price)  # доступ к атрибутам объекта

# Возвращает словари
products = Product.objects.filter(is_active=True).values("name", "price")
for p in products:
    print(p["name"], p["price"])  # доступ по ключу словаря
-- values("name", "price"), только нужные колонки
SELECT "shop_product"."name", "shop_product"."price"
FROM "shop_product"
WHERE "shop_product"."is_active" = true
ORDER BY "shop_product"."name";

Без аргументов values() возвращает все поля как словари. С аргументами, только указанные. Меньше данных из БД, меньше нагрузка на сеть и память.

Синтаксис __ (двойное подчеркивание)

Django использует __ как разделитель для обхода связей между моделями и задания условий фильтрации. Общий принцип:

поле__поле_связанной_модели
поле__тип_условия
# Обход связи: перейти от Product к Category и взять её name
Product.objects.values("category__name")

# Тип условия: price больше 500
Product.objects.filter(price__gt=500)

# Комбинация: поле связанной модели + условие
Product.objects.filter(category__slug="phones")

Глубина вложенности не ограничена, можно идти по цепочке связей:

# Продукты заказов конкретного пользователя
OrderItem.objects.filter(order__user__username="admin")

Полный список типов условий (__gt, __contains, __in и другие) разберем в уроке 2.3.


values() через связи

# Получить название продукта и название его категории
Product.objects.values("name", "category__name")
SELECT "shop_product"."name", "shop_category"."name"
FROM "shop_product"
INNER JOIN "shop_category" ON "shop_product"."category_id" = "shop_category"."id"
ORDER BY "shop_product"."name";

Django автоматически делает JOIN. Колонка называется category__name (двойное подчеркивание), так же как в словаре.

Когда использовать values()

  • Нужны только данные (для сериализации, экспорта, агрегаций), а не объекты модели
  • Нужно снизить потребление памяти при большой выборке
  • Работаете с агрегациями через annotate(), values() + annotate(), стандартный паттерн для GROUP BY

values_list() - кортежи или отдельные значения

values_list() возвращает кортежи вместо словарей. Используйте его когда результат передается в другой QuerySet или нужен простой список значений, а не структура для сериализации.

values() values_list()
Результат {"name": "iPhone", "price": ...} ("iPhone", Decimal("..."))
Когда сериализация, передача в шаблон список id, передача в filter(__in=...)
# Список кортежей
Product.objects.values_list("name", "price")
# [("iPhone 15", Decimal("999.99")), ("Samsung Galaxy S24", Decimal("899.99")), ...]

# flat=True, список значений (только одно поле)
Product.objects.values_list("name", flat=True)
# ["iPhone 15", "Samsung Galaxy S24", ...]

# named=True - именованные кортежи
Product.objects.values_list("name", "price", named=True)
# [Row(name="iPhone 15", price=Decimal("999.99")), ...]
-- values_list("name", "price")
SELECT "shop_product"."name", "shop_product"."price"
FROM "shop_product"
ORDER BY "shop_product"."name";

Часто нужно передать результат в другой QuerySet через __in:

# Получить id заказов пользователя и передать в следующий запрос
order_ids = Order.objects.filter(user=user).values_list("id", flat=True)
items = OrderItem.objects.filter(order_id__in=order_ids)

flat=True работает только с одним полем. Для нескольких полей используйте values_list()
без flat или values().


only() и defer() - контроль над колонками объектов

values() и values_list() возвращают словари и кортежи. Если нужны именно объекты модели, но только с частью полей, используйте only() и defer().

only() - загрузить только указанные поля

# Загрузить только name и price
products = Product.objects.only("name", "price")
for p in products:
    print(p.name, p.price)  # из кеша, без запроса
    print(p.description)    # ДОПОЛНИТЕЛЬНЫЙ запрос к БД
-- only("name", "price") - pk загружается всегда
SELECT "shop_product"."id", "shop_product"."name", "shop_product"."price"
FROM "shop_product"
ORDER BY "shop_product"."name";

Если обратиться к полю, которое не было загружено через only(), Django сделает дополнительный SELECT для этого объекта. Это называется deferred field: отложенное поле.

defer() - исключить указанные поля

# Загрузить все поля кроме description (тяжелое текстовое поле)
products = Product.objects.defer("description")
SELECT "shop_product"."id", "shop_product"."name", "shop_product"."slug",
       "shop_product"."price", "shop_product"."stock", "shop_product"."is_active",
       "shop_product"."created_at", "shop_product"."updated_at", "shop_product"."category_id"
FROM "shop_product"
ORDER BY "shop_product"."name";
-- description НЕ в SELECT

defer() удобен когда у модели есть одно-два тяжелых поля (большой TextField, JSONField), которые не нужны в конкретном запросе.

Ловушка only() и defer()

products = Product.objects.only("name", "price")
for p in products:
    # Каждое обращение к отложенному полю = отдельный SELECT
    print(p.description)  # SELECT description FROM shop_product WHERE id = X

Это может создать N+1 на отложенных полях. Используйте only() и defer() только когда точно знаете, что не будете обращаться к исключенным полям.

Подробнее с примерами производительности в уроке 5.3.


Методы немедленно выполняющие запрос

Не все методы QuerySet ленивые. Эти методы выполняют SQL немедленно и возвращают не QuerySet:

qs = Product.objects.filter(is_active=True)

qs.count()    # int - SELECT COUNT(*) ...
qs.exists()   # bool - SELECT 1 ... LIMIT 1
qs.first()    # объект или None
qs.last()     # объект или None
qs.get(...)   # объект или исключение

list(qs)      # список объектов
bool(qs)      # выполняет запрос для проверки наличия

# aggregate() словарь с результатами агрегации
from django.db.models import Avg
qs.aggregate(avg_price=Avg("price"))  # {"avg_price": Decimal("...")}

count() vs len()

# count() - SELECT COUNT(*) эффективнее, не грузит объекты
count = Product.objects.filter(is_active=True).count()

# len() загружает все объекты в память, затем считает
count = len(Product.objects.filter(is_active=True))
-- count()
SELECT COUNT(*) FROM shop_product WHERE is_active = true;

-- len() - загружает всё
SELECT * FROM shop_product WHERE is_active = true ORDER BY name;

Всегда используйте count() когда нужно только количество.

exists() vs bool()

# exists() - SELECT 1 ... LIMIT 1 - проверяет только наличие
if Product.objects.filter(is_active=True, stock__gt=0).exists():
    ...

# bool() - загружает все объекты
if Product.objects.filter(is_active=True, stock__gt=0):
    ...
-- exists()
SELECT 1 FROM shop_product WHERE is_active = true AND stock > 0 LIMIT 1;

exists() останавливается при первом найденном объекте. Всегда используйте exists() вместо
bool() для проверки наличия.


Сводная таблица методов

Метод Возвращает Запрос
all() QuerySet Ленивый
filter() QuerySet Ленивый
exclude() QuerySet Ленивый
values() QuerySet[dict] Ленивый
values_list() QuerySet[tuple] Ленивый
only() QuerySet Ленивый
defer() QuerySet Ленивый
order_by() QuerySet Ленивый
get() объект Немедленный
first() объект/None Немедленный
last() объект/None Немедленный
count() int Немедленный
exists() bool Немедленный
aggregate() dict Немедленный
in_bulk() dict Немедленный

in_bulk() - словарь объектов по pk

in_bulk() получает список id и возвращает словарь {id: объект}. Полезен когда нужно быстро найти объекты по набору id без итерации:

# Получить продукты по списку id как словарь
product_ids = [1, 5, 12, 47]
products_map = Product.objects.in_bulk(product_ids)
# {1: <Product: iPhone 15>, 5: <Product: MacBook>, 12: <Product: ...>, 47: <Product: ...>}

# Доступ по id
product = products_map[5]
SELECT * FROM shop_product
WHERE id IN (1, 5, 12, 47)
ORDER BY shop_product.name;

Без id_list, загружает все объекты (как all()):

all_products = Product.objects.in_bulk()
# {1: <Product>, 2: <Product>, ...}

Можно использовать другое поле для ключа, если оно уникальное:

products_by_slug = Product.objects.in_bulk(
    ["iphone-15", "macbook-pro"],
    field_name="slug",
)
# {"iphone-15": <Product>, "macbook-pro": <Product>}
SELECT * FROM shop_product
WHERE slug IN ('iphone-15', 'macbook-pro')
ORDER BY name;

Применяйте когда нужно многократно обратиться к объектам по id. Без in_bulk это N запросов, с in_bulk один:

# Без in_bulk: запрос на каждый элемент цикла
for item in order_items:
    product = Product.objects.get(id=item.product_id)  # SELECT на каждой итерации
    print(product.name)

# С in_bulk: 1 запрос, затем доступ по ключу за O(1)
order_product_ids = OrderItem.objects.filter(
    order__user=user
).values_list("product_id", flat=True)

products = Product.objects.in_bulk(list(order_product_ids))

for item in order_items:
    product = products[item.product_id]  # без запроса
    print(product.name)

Практическое задание

Все задания выполняй в shell с включенным логированием SQL, чтобы видеть реальные запросы.

  1. Получите все активные продукты с ценой выше 500. Проверьте через лог SQL что запрос выполнился только при итерации, а не при создании QuerySet.

  2. Сравните SQL для этих двух вариантов, в чем разница?

   Product.objects.filter(is_active=True).count()

   len(Product.objects.filter(is_active=True))
  1. Получите список названий всех категорий через values_list("name", flat=True). Сколько запросов SQL?

  2. Получите имена и цены продуктов категории "phones" через values("name", "price", "category__name"). Посмотрите на SQL, есть ли JOIN?

  3. Попробуйте Product.objects.only("name") и в цикле обратитесь к p.description. Посчитайте количество запросов SQL для 5 продуктов. Почему их столько?


Возможные ошибки

Лишняя загрузка данных вместо count()

# Загружает все объекты в память ради подсчета
total = len(Product.objects.filter(is_active=True))

# Правильно
total = Product.objects.filter(is_active=True).count()

bool() вместо exists()

# Загружает объекты ради проверки наличия
if Product.objects.filter(stock=0):
    notify_manager()

# Правильно
if Product.objects.filter(stock=0).exists():
    notify_manager()

Повторное создание QuerySet в цикле

# Два разных QuerySet = два запроса
for product in Product.objects.filter(is_active=True):
    if product in Product.objects.filter(price__gt=500):  # новый запрос на каждой итерации!
        ...

# Правильно - один QuerySet
expensive_active = Product.objects.filter(is_active=True, price__gt=500)
for product in expensive_active:
    ...

Обращение к отложенному полю в цикле

# N дополнительных запросов для N объектов
products = Product.objects.only("name")
for p in products:
    print(p.description)  # запрос для каждого объекта

Связь со следующим уроком

В уроке 2.3 разберем lookups, все виды условий фильтрации: __gt, __lt, __contains, __in, __range и другие. Также покажу как фильтровать через связанные модели используя двойное подчеркивание.


<< Урок 2.1

Урок 2.3 >>


Подписывайтесь на мой Telegram канал

Если вам нужен ментор и вы хотите научиться веб-разработке, узнать подробнее

Авторизуйтесь, чтобы оставить комментарий.

Комментариев: 0

Нет комментариев.

Тут может быть ваша реклама

Пишите info@aisferaic.ru

Похожие туториалы