For a query set of a single element, indexing 0th element of a (query_set[0]) and and query_set.first() give different values - Django 2.2.7

Viewed 62

I am running this code:

        city_state_location = (
            Location.objects.filter(city=city, state=state)
            .values_list("city")
            .annotate(long=Avg("longitude"), lat=Avg("latitude"))
        )
        print(
            city_state_location,
            city_state_location[0],
            city_state_location.first(),
            len(city_state_location),
        )

Which outputs:

<QuerySet [('New York', -73.9422666666667, 40.8382)]>
('New York', -73.9422666666667, 40.8382)
('New York', -73.9501, 40.8252)
1

Why does this happen? The query set only contains one element so I'm confused why indexing the first element is any different then calling .first()

This is running on Django 2.2.7

1 Answers

Why does this happen?

Contrary to popular belief, .first() and [0] are different. Indeed, .first() will add a .order_by('pk') clause in case you do not order the queryset properly, as a result there is no GROUP BY city, since the primary key is part of the ordering process, so the first item is simply the first Location object ordered by primary key that satisfies the given filtering, and the Avg('longitude') and Avg('latitude'). Indeed, if we look at the source code of .first() [GitHub], we see:

def first(self):
    """Return the first object of a query or None if no match is found."""
    for obj in (self if self.ordered else self.order_by('pk'))[:1]:
        return obj

Thus will return the longitude and latitude for that first item.

Using [0] on the other hand, will not add an extra .order_by('pk'), so the item that is returned is that of the first group of items satisfying the filter condition, and that will thus determine the average of all items that point to 'New York' in this case.

If you would have used city_state_location.order_by('pk')[0], then the same would happen as using .first().

Related