• ↑↓ pour naviguer
  • pour ouvrir
  • pour sélectionner
  • ⌘ ⌥ ↵ pour ouvrir dans un panneau
  • ←→ pour naviguer
  • esc pour rejeter
⌘ '
raccourcis clavier

Formal Definition

A QuerySet is a lazy, chainable collection of database queries represented by django.db.models.QuerySet, providing methods for filtering (filter, exclude), ordering (order_by), slicing, aggregation (aggregate, annotate), and relationship traversal (select_related, prefetch_related) that only executes SQL when evaluated.

Explanation

The QuerySet API solves the problem of building complex database queries programmatically without writing raw SQL. It uses lazy evaluation — chaining .filter().exclude().order_by() builds an internal query plan but executes no SQL until iteration, list(), len(), bool(), or explicit .all(). This allows dynamic query composition based on runtime conditions while deferring expensive database round-trips.

How It Works

  1. Manager accessModel.objects returns a Manager with base QuerySet
  2. Chaining filters — Each method returns new QuerySet with modified query attribute
  3. Query compilation — On evaluation, QuerySet.query compiles to SQL via SQLCompiler
  4. SQL execution — Database cursor executes; rows fetched
  5. Result hydration — Rows converted to model instances (or dicts/values_list tuples)
  6. Caching — Evaluated QuerySet caches results; re-iteration uses cache

Visual Explanation

queryset_api Manager Post.objects → Manager BaseQS QuerySet (query=SELECT * FROM post) Manager->BaseQS 1. Base queryset Filtered .filter(published=True) → QuerySet (WHERE published=1) BaseQS->Filtered 2. Chain filter Annotated .annotate(comment_count=Count('comments')) → QuerySet (GROUP BY) Filtered->Annotated 3. Chain annotate Ordered .order_by('-created') → QuerySet (ORDER BY) Annotated->Ordered 4. Chain order_by Evaluated list(qs) or for p in qs → SQL EXECUTED Ordered->Evaluated 5. Evaluate (lazy) Results [Post, Post, ...] Cached in qs._result_cache Evaluated->Results 6. Hydrate & cache

Semantic Network

semantic_queryset_api THIS QuerySet API PRE1 Models / ORM THIS--PRE1 built from PRE2 Model Managers THIS--PRE2 built from PRE3 Field Lookups THIS--PRE3 built from OUT1 Filtering / Exclusion THIS--OUT1 builds into OUT2 Aggregation / Annotation THIS--OUT2 builds into OUT3 Relationship Optimization THIS--OUT3 builds into OUT4 Bulk Operations THIS--OUT4 builds into OUT5 Raw SQL Escape Hatch THIS--OUT5 builds into CON1 SQLAlchemy Query API THIS--CON1 contrasts with CON2 Raw psycopg2 THIS--CON2 contrasts with REL1 Transactions (atomic) THIS--REL1 related REL2 Pagination (Paginator) THIS--REL2 related

Key Properties

  • Laziness: No SQL until evaluation; qs = Post.objects.all() hits DB zero times
  • Immutability: Each method returns new QuerySet; original unchanged
  • Caching: First evaluation populates _result_cache; subsequent use cached
  • Field lookups: field__lookup syntax — exact, iexact, contains, icontains, gt, gte, lt, lte, in, startswith, endswith, range, date, year, month, day, isnull, regex
  • Optimization: select_related (FK, O2O → JOIN), prefetch_related (M2M, reverse FK → separate query + Python join)

Connections

  • Built from: ORM — QuerySet operates on model tables
  • Built from: Model Managers — Entry point via Model.objects
  • Built from: Field Lookups__ syntax for filters
  • Builds into: Exclusionfilter(), exclude(), Q() objects
  • Builds into: Annotationaggregate(), annotate(), Count, Sum, Avg
  • Builds into: Relationship Optimizationselect_related, prefetch_related
  • Builds into: Bulk Operationsbulk_create, bulk_update, update(), delete()
  • Contrasts with: SQLAlchemy Query — Explicit session, more flexible joins
  • Contrasts with: Raw SQL — Full control, no ORM overhead
  • Related: Transactionsatomic() for multi-query atomicity
  • Related: PaginationPaginator slices QuerySet for pages

Edge Cases & Gotchas

  • QuerySet cloning: qs.filter(...) returns new QuerySet; modifying qs in place doesn’t work
  • Slicing evaluates: qs[:10] executes SQL with LIMIT; qs[5:10] uses OFFSET/LIMIT
  • len(qs) vs qs.count(): len() evaluates and caches; count() always does SELECT COUNT(*)
  • exists() vs bool(qs): exists() does SELECT 1 ... LIMIT 1; bool() evaluates full QuerySet
  • M2M filter() vs exclude(): Post.objects.filter(tags__name='django') vs exclude(tags__name='django')exclude matches posts with NO matching tags, not posts where ALL tags don’t match