Skip to content Skip to sidebar Skip to footer

Why Do Queryset[0] And Queryset.first() Return Different Records?

I discovered today that I can access elements in a queryset by referencing them with an index, i.e. queryset[n]. However, immediately after I discovered that queryset[0] does not r

Solution 1:

There is a small semantical difference between qs[0] and qs.first(). If you did not specify an order in the queryset yourself, then Django will order the queryset itself by primary key before fetching the first element.

Furthermore .first() will return None if the queryset is empty. Whereas qs[0] will raise an IndexError.

The claim that .first() is faster however is notTrue. In fact if you use qs[n], then Django will, behind the curtains fetch the record by slicing with qs[n:n+1], hence it will, given the database backend supports this, make a query with LIMIT 1 OFFSET n, and thus fetch one record, just like .first() will do. If the queryset is already retrieved, it will furthermore make no extra queries at all, since the data is already cached.

You can see the implementation on GitHub:

deffirst(self):
        """
        Returns the first object of a query, returns None if no match is found.
        """
        objects = list((self if self.ordered else self.order_by('pk'))[:1])
        if objects:
            return objects[0]
        returnNone

As you can see, if the queryset is already ordered (self.ordered is True, then we take self[:1], check if there is a record, and if so return it. If not we return None.

The code for retrieving an item at a specific index is more cryptic. It essentially will set the limits from k to k+1, materialize the item, and return the first item, as we can see in the source code [GitHub]:

def__getitem__(self, k):
        """
        Retrieves an item or slice from the set of results.
        """ifnotisinstance(k, (slice,) + six.integer_types):
            raise TypeError
        assert ((notisinstance(k, slice) and (k >= 0)) or
                (isinstance(k, slice) and (k.start isNoneor k.start >= 0) and
                 (k.stop isNoneor k.stop >= 0))), \
            "Negative indexing is not supported."if self._result_cache isnotNone:
            return self._result_cache[k]

        ifisinstance(k, slice):
            qs = self._clone()
            if k.start isnotNone:
                start = int(k.start)
            else:
                start = Noneif k.stop isnotNone:
                stop = int(k.stop)
            else:
                stop = None
            qs.query.set_limits(start, stop)
            returnlist(qs)[::k.step] if k.step else qs

        qs = self._clone()
        qs.query.set_limits(k, k + 1)
        returnlist(qs)[0]

Post a Comment for "Why Do Queryset[0] And Queryset.first() Return Different Records?"