Часто мне кажется, что я хочу получить первый объект из набора запросов в Django или вернуть None
, если его нет. Есть много способов сделать это, что все работает. Но мне интересно, какая из них самая результативная.
qs = MyModel.objects.filter(blah = blah)
if qs.count() > 0:
return qs[0]
else:
return None
Это приводит к двум вызовам базы данных? Это кажется расточительным. Это быстрее?
qs = MyModel.objects.filter(blah = blah)
if len(qs) > 0:
return qs[0]
else:
return None
Другой вариант:
qs = MyModel.objects.filter(blah = blah)
try:
return qs[0]
except IndexError:
return None
Это генерирует единый вызов базы данных, что хорошо. Но требует много времени создания объекта исключения, что очень важно для работы с памятью, когда все, что вам действительно нужно, - это тривиальный if-test.
Как я могу сделать это с помощью всего одного вызова базы данных и без взлома памяти с объектами исключений?
Django 1.6 (выпущен в ноябре 2013 года) представил методы удобства first()
и last()
, которые проглотите полученное исключение и верните None
, если запрос не возвращает никаких объектов.
first()
и last()
применяют предложение ORDER BY
в запросе. Это сделает результаты детерминированными, но, скорее всего, замедлит запрос.
Правильный ответ
Entry.objects.all()[:1].get()
, который можно использовать в:
Entry.objects.filter()[:1].get()
Вы не захотите сначала превратить его в список, потому что это приведет к полному вызову базы данных всех записей. Просто сделайте это, и он потянет только первый. Вы даже можете использовать .order_by
, чтобы убедиться, что вы получите первое, что хотите.
Обязательно добавьте .get()
, иначе вы вернете QuerySet, а не объект.
r = list(qs[:1])
if r:
return r[0]
return None
LIMIT 1
к запросу, и я не знаю, что вы можете сделать что-то лучше, чем это. Однако внутренне __nonzero__
в QuerySet
реализован как try: iter(self).next() except StopIteration: return false...
поэтому он не исключает исключения.
QuerySet.__nonzero__()
никогда не вызывается, поскольку QuerySet
преобразуется в list
перед проверкой на достоверность. Однако могут возникнуть и другие исключения.
Теперь, в Django 1.9 у вас есть метод first()
для запросов.
YourModel.objects.all().first()
Это лучший способ, чем .get()
или [0]
, потому что он не генерирует исключение, если набор запросов пуст. Для этого вам не нужно проверять с помощью exists()
Если вы планируете часто получать первый элемент - вы можете расширить QuerySet в этом направлении:
class FirstQuerySet(models.query.QuerySet):
def first(self):
return self[0]
class ManagerWithFirstQuery(models.Manager):
def get_query_set(self):
return FirstQuerySet(self.model)
Определите модель следующим образом:
class MyModel(models.Model):
objects = ManagerWithFirstQuery()
И используйте его следующим образом:
first_object = MyModel.objects.filter(x=100).first()
Это может быть как
obj = model.objects.filter(id=emp_id)[0]
или
obj = model.objects.latest('id')
Вы должны использовать методы django, например, существующие. Его там для вас, чтобы использовать его.
if qs.exists():
return qs[0]
return None
Это может сработать:
def get_first_element(MyModel):
my_query = MyModel.objects.all()
return my_query[:1]
если он пуст, затем возвращает пустой список, иначе он возвращает первый элемент внутри списка.
len()
для.count()
, всегда используйте.count()
.