23.4 What changed in querysets
The changes to querysets from Django 5.0 to this commit: fetch modes and QuerySet (6.1), beside which QuerySet with no arguments is deprecated; the orderings of QuerySet.first, QuerySet.last and combined querysets (6.1) and of the columns of QuerySet.values (5.2); and smaller changes to QuerySet, QuerySet, QuerySet.in_bulk, QuerySet, the iterators, prefetching, QuerySet.raw and QuerySet.explain.
A queryset still builds its query and makes instances from the rows as QuerySets tells. What changed most is what an instance does when it is read for a value the queryset did not load, which since 6.1 the queryset decides. Of the others, some alter a default in silence and some remove what an earlier release deprecated.
Fetch modes
Until 6.1, reading a deferred field, or a related object that an instance had not cached, ran a query for that one instance. Since 6.1 the read is handed to the instance’s fetch mode, an object that decides how the value is fetched. FETCH_ONE, the default, fetches it for the one instance, which the release notes call Django’s existing behaviour. FETCH_PEERS fetches it at once for every instance that the same run of the queryset made, the instance’s peers. FETCH_RAISE refuses, with FieldFetchBlocked, a FieldError. The notes give the new modes their purposes: the peers bring most cases of the “N+1 queries problem” down to two queries, and the refusal keeps unintended queries out of code where they would cost.
QuerySet sets a mode on a clone of the queryset, which hands it to each instance made from a row through a keyword argument that 6.1 added to Model.from_db, fetch_mode=None. Deferred fields and fetch modes in a queryset tells the mechanism, and Reading and setting related objects what a relation’s descriptor does under each mode. Here four sailors, two to a ship, have their ships read in a loop:
# fetch_modes.py at this commit: [s.ship.name for s in Sailor.objects.fetch_mode(mode).order_by("id")]
FETCH_ONE 5 statements ['Petrel', 'Petrel', 'Gannet', 'Gannet']
FETCH_PEERS 2 statements ['Petrel', 'Petrel', 'Gannet', 'Gannet']
FETCH_RAISE 1 statement FieldFetchBlocked: Fetching of Sailor.ship blocked.
Under the default each sailor’s ship was fetched for that sailor, Petrel twice; under the peers the first read fetched the four sailors’ ships, by a prefetch of one statement.
In 6.1 a queryset of a model whose own from_db accepts neither fetch_mode= nor arbitrary keywords crashed when iterated; 6.1.1 made such overrides work again, and deprecated them. At this commit _get_from_db inspects the override, warns with RemovedInDjango2028Warning, and calls it without the mode, so its instances are not given the queryset’s.
Beside fetch modes, 6.1 deprecated select_related with no arguments, which has the compiler follow the relations that cannot be null. The call warns with RemovedInDjango2028Warning, the notes name FETCH_PEERS as the other way to the related objects, and the deprecation timeline has the call raise TypeError in Django 2028. What still warns: the deprecations and the releases that end them lists both deprecations.
The notes of 6.1.2, a release expected and not yet made at this commit, list as fixed a bug of 6.1 in which FETCH_PEERS did not batch the instances that select_related loaded. At this commit each RelatedPopulator, which makes the instances of one joined model, keeps a list of peers of its own under a mode that tracks them, so the ships joined to one run of sailors are each other’s peers.
Orderings
QuerySet.first and QuerySet.last give a queryset with no ordering one by primary key. Until 6.1 they did so also for a queryset whose ordering had been cleared on purpose, by QuerySet with no arguments. Since 6.1 they read the mark that call leaves, Query turned off, and use the queryset as it stands (QuerySet.first; get, first, count and the other methods that run a query at once).
A queryset made by QuerySet.union, QuerySet or QuerySet has the model’s default ordering since 6.1: QuerySet clears the ordering of the combined query and turns its default back on. The notes give the consequence: where values or values_list has not selected a field of that ordering, the statement fails with DatabaseError, and order_by() with no arguments on the combined queryset clears the default. Ship is ordered by name:
# combined.py at this commit, on SQLite; left and right are each Ship.objects.order_by().values("tonnage")
list(left.union(right)) DatabaseError: ORDER BY term does not match any column in the result set.
list(left.union(right).order_by()) [{'tonnage': 480}, {'tonnage': 1200}]
The parts were cleared first: each keeps a default ordering of its own, which order_by() on the combined queryset leaves in place, and a backend that cannot order the parts of a compound statement, as SQLite cannot, refuses that with another DatabaseError (SQLCompiler; Combining querysets: the operators and union).
QuerySet (6.1) is a property beside QuerySet.ordered, true only where the ordering is deterministic (QuerySet; Annotations, ordering and the other methods that change the query).
Since 5.2 the columns of the statement of QuerySet.values and QuerySet stand in the order the names were given; the notes say the order had followed counterintuitive rules that made combining such querysets with union unpredictable. Query.set_values records each name, with where its column comes from, in Query.selected, and SQLCompiler writes the select list from it. Given no names, values leaves selected as None, and the compiler’s own order stands: the extra columns, the model’s fields, the annotations (values and values_list: rows as dictionaries and tuples).
The smaller changes
| the subject | what changed | release | where now |
|---|---|---|---|
Query |
what changed takes create_defaults=, the values for a row it has to make, defaults= serving for both where that is None; so does Query. A condition on a field named create_defaults is now taken for the argument unless written "create_ |
release 5.0 | where now Creating, updating and deleting through a queryset |
Query |
what changed with update_, sets each instance’s primary key from the rows the statement returns, where the backend can return rows from a bulk insert |
release 5.0 | where now Query django/bulk_create and bulk_update |
QuerySet.in_bulk |
what changed can follow values and values_list, and selects the key’s field where they did not |
release 6.1 | where now QuerySet.in_bulk django/ |
Query |
what changed flat=True with no field name warns with Removed and takes the attname of the model’s first concrete field; the timeline has it raise TypeError |
release 6.1 | where now Query django/ |
Query |
what changed with prefetch lookups and no chunk_size=, raises ValueError |
release 5.0 | where now prefetch_related: related objects in a second query |
Query |
what changed follows prefetch lookups a chunk at a time, through aprefetch_, new beside it (5.0); with them and no chunk_size=, warns with Removed and takes 2,000 (6.2) |
release 5.0, 6.2 | where now The asynchronous methods of a queryset |
| the prefetcher’s methods | what changed get_prefetch_queryset of the related managers and descriptors, and the method get_current_queryset of Prefetch, deprecated in 5.0, are gone: get_prefetcher looks for get_ alone |
release 6.0 | where now get_prefetcher django/prefetch_related: related objects in a second query |
QuerySet.raw |
what changed serves a model with a Composite, whose every field has to be among the columns |
release 6.0 | where now Raw querysets |
QuerySet.explain |
what changed on PostgreSQL, passes on generic_ (5.1), memory=True and serialize="text" (5.2); the notes ask PostgreSQL 17 for the last two, and the code checks no version |
release 5.1, 5.2 | where now Database django/ |
| a filter across a relation | what changed an instance whose primary key is not set, Model._is_pk_set false, is refused with ValueError; one given a key and never saved is not |
release 5.0 | where now get_ django/ |