Django Window Functions vs GROUP BY: Chainable QuerySets

Django ORM gives you two ways to add a computed value across a set of rows: annotate() with a classic aggregation (Max, Count, Sum…) or annotate() with a Window function. On the surface they look similar. In practice, they behave in fundamentally different ways — and picking the wrong one can break your entire filtering chain. GROUP BY with annotate(): rows that collapse When you combine values() and annotate() with an aggregation, Django generates a GROUP BY in SQL. The result: rows get merged, and you end up with one row per group. ...

May 4, 2026 · 4 min · Anthony

Django in_bulk(): why it beats filter() for bulk lookups

When you have a list of identifiers and want to retrieve the corresponding instances, the usual reflex in Django is filter(pk__in=[...]). It works — one SQL query. But in_bulk() is an often-overlooked ORM optimization: it returns a dictionary {id: instance} instead of a QuerySet, which fundamentally changes how you access results. Where filter() forces an O(n) traversal to find an object by ID, in_bulk() gives direct O(1) access. in_bulk() signature and behavior QuerySet.in_bulk(id_list=(), *, field_name='pk') id_list: list of identifiers to retrieve. If omitted (called without arguments), returns all objects in the table. field_name: field used as the dictionary key. Must have unique=True, otherwise Django raises a ValueError. The generated SQL is a simple WHERE pk IN (...) clause — one query regardless of list size. ...

May 4, 2026 · 4 min · Anthony

Newsletter

Reçois les nouveaux articles directement dans ta boite mail.

Pas de spam. Désabonnement en un clic.