Often I find myself wanting to get the first object from a queryset in Django, or return None
if there aren't any. There are lots of ways to do this which all work. But I'm wondering which is the most performant.
qs = MyModel.objects.filter(blah = blah)
if qs.count() > 0:
return qs[0]
else:
return None
Does this result in two database calls? That seems wasteful. Is this any faster?
qs = MyModel.objects.filter(blah = blah)
if len(qs) > 0:
return qs[0]
else:
return None
Another option would be:
qs = MyModel.objects.filter(blah = blah)
try:
return qs[0]
except IndexError:
return None
This generates a single database call, which is good. But requires creating an exception object a lot of the time, which is a very memory-intensive thing to do when all you really need is a trivial if-test.
How can I do this with just a single database call and without churning memory with exception objects?
len()
on querysets, always use .count()
.
first()
and last()
convenience methods: docs.djangoproject.com/en/dev/ref/models/querysets/#first
Django 1.6 (released Nov 2013) introduced the convenience methods first()
and last()
which swallow the resulting exception and return None
if the queryset returns no objects.
You can use array slicing:
Entry.objects.all()[:1].get()
Which can be used with .filter()
:
Entry.objects.filter()[:1].get()
You wouldn't want to first turn it into a list because that would force a full database call of all the records. Just do the above and it will only pull the first. You could even use .order_by()
to ensure you get the first you want.
Be sure to add the .get()
or else you will get a QuerySet back and not an object.
Entry.objects.all()[0]
??
r = list(qs[:1])
if r:
return r[0]
return None
LIMIT 1
to the query, and I don't know that you can do any better than this. However, internally __nonzero__
in QuerySet
is implemented as try: iter(self).next() except StopIteration: return false...
so it doesn't escape the exception.
QuerySet.__nonzero__()
is never called since the QuerySet
is converted to a list
before checking for trueness. Other exceptions may still occur however.
StopIteration
exception.
__iter__
to get a new iterator object and call it's next
method until StopIteration
is thrown. So definitively there is gonna be an exception somewhere ;)
Now, in Django 1.9 you have first()
method for querysets.
YourModel.objects.all().first()
This is a better way than .get()
or [0]
because it does not throw an exception if queryset is empty, Therafore, you don't need to check using exists()
This could work as well:
def get_first_element(MyModel):
my_query = MyModel.objects.all()
return my_query[:1]
if it's empty, then returns an empty list, otherwise it returns the first element inside a list.
If you plan to get first element often - you can extend QuerySet in this direction:
class FirstQuerySet(models.query.QuerySet):
def first(self):
return self[0]
class ManagerWithFirstQuery(models.Manager):
def get_query_set(self):
return FirstQuerySet(self.model)
Define model like this:
class MyModel(models.Model):
objects = ManagerWithFirstQuery()
And use it like this:
first_object = MyModel.objects.filter(x=100).first()
It can be like this
obj = model.objects.filter(id=emp_id)[0]
or
obj = model.objects.latest('id')
You should use django methods, like exists. Its there for you to use it.
if qs.exists():
return qs[0]
return None
Success story sharing
first()
andlast()
enforce anORDER BY
clause on a query. It will make the results deterministic but will most probably slow the query down.ORDER BY
@Phil Krylov pointed out, which[:1]
avoids.