# QuerySets: Filtering and Laziness — Django / FastAPI

Source: https://www.skillbyai.com/en/django-fastapi/o-qs

> Chainable, lazy queries.

## filter, exclude, get, lookups

A **QuerySet** represents a query; methods like `filter`, `exclude`, `order_by` and `values_list` return new QuerySets without touching the database. The query runs when you iterate, slice with a step, call `list`, `count`, `exists` or `get`. Field lookups use double underscores (`price__lt`, `category__name`), including across relations. `get` returns exactly one object or raises DoesNotExist or MultipleObjectsReturned.

## Expressive, lazy, efficient

QuerySets build SQL lazily; select_related, aggregation and F expressions keep queries efficient.

![Three ideas: QuerySets, avoiding N+1, aggregation and F expressions.](assets/figures/django-fastapi/section-5-map.svg) — Figure 5.1 — QuerySets, N+1 and aggregation.

## Building and running queries, run

I ran this script with python manage.py shell --no-imports in the demo project (Django 6.1.1, SQLite). The filter builds SQL without running it (the WHERE clause is printed, with the model's default ordering); iterating runs it. get returns one product, a lookup across the relation finds in-stock Home products, and exclude counts one product out of stock.

```python
from decimal import Decimal
from catalog.models import Category, Product

Product.objects.all().delete(); Category.objects.all().delete()
office = Category.objects.create(name="Office")
home = Category.objects.create(name="Home")
Product.objects.bulk_create([
    Product(category=office, name="Pen", price=Decimal("899")),
    Product(category=office, name="Notebook", price=Decimal("120")),
    Product(category=home, name="Lamp", price=Decimal("1499")),
    Product(category=home, name="Mug", price=Decimal("250"), in_stock=False),
])
qs = Product.objects.filter(price__lt=1000)          # lazy: no query yet
print("SQL:", str(qs.query).split(" WHERE ")[1])
print([p.name for p in qs])                           # the query runs here
print(Product.objects.get(name="Lamp"))
print(Product.objects.filter(category__name="Home", in_stock=True).values_list("name", flat=True)[:])
print(Product.objects.exclude(in_stock=True).count(), "out of stock")
```

Output:

```
SQL: "catalog_product"."price" < 1000 ORDER BY "catalog_product"."name" ASC
['Mug', 'Notebook', 'Pen']
Lamp (1499.00)
<QuerySet ['Lamp']>
1 out of stock
```

## Use exists() and count() wisely

exists() is cheaper than fetching rows just to check presence; avoid len(qs) when you only need a count.

**Quiz:** When does a QuerySet hit the database?

- [x] When it is evaluated, for example iterated, listed, counted or fetched with get
- [ ] When filter() is called
- [ ] When the model is imported
- [ ] Never

*Answer:* When it is evaluated, for example iterated, listed, counted or fetched with get. QuerySets are lazy.
