Самый быстрый способ получить первый объект из набора запросов в Django?

126

Часто мне кажется, что я хочу получить первый объект из набора запросов в 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.

Как я могу сделать это с помощью всего одного вызова базы данных и без взлома памяти с объектами исключений?

  • 15
    Практическое правило. Если вы беспокоитесь о минимизации обходов БД, не используйте len() для .count() , всегда используйте .count() .
  • 6
    «создание объекта исключения в большинстве случаев, что требует очень много памяти» - если вас беспокоит создание одного дополнительного исключения, то вы делаете это неправильно, поскольку Python использует исключения повсеместно. Вы на самом деле отметили, что это интенсивно использует память в вашем случае?
Показать ещё 4 комментария
Теги:
performance
django-models

8 ответов

247
Лучший ответ

Django 1.6 (выпущен в ноябре 2013 года) представил методы удобства first() и last(), которые проглотите полученное исключение и верните None, если запрос не возвращает никаких объектов.

  • 1
    он не выполняет [: 1], поэтому он не такой быстрый (если вам все равно не нужно оценивать весь набор запросов).
  • 7
    также first() и last() применяют предложение ORDER BY в запросе. Это сделает результаты детерминированными, но, скорее всего, замедлит запрос.
126

Правильный ответ

Entry.objects.all()[:1].get()

, который можно использовать в:

Entry.objects.filter()[:1].get()

Вы не захотите сначала превратить его в список, потому что это приведет к полному вызову базы данных всех записей. Просто сделайте это, и он потянет только первый. Вы даже можете использовать .order_by, чтобы убедиться, что вы получите первое, что хотите.

Обязательно добавьте .get(), иначе вы вернете QuerySet, а не объект.

  • 9
    Вам все равно нужно будет обернуть его в попытке ... за исключением ObjectDoesNotExist, который похож на оригинальный третий вариант, но с нарезкой.
  • 1
    Какой смысл устанавливать LIMIT, если ты собираешься в конце вызвать get ()? Пусть ORM и компилятор SQL решат, что лучше для его бэкэнда (например, в Oracle Django эмулирует LIMIT, так что это будет больно, а не поможет).
Показать ещё 4 комментария
46
r = list(qs[:1])
if r:
  return r[0]
return None
  • 1
    Если вы включите трассировку, я почти уверен, что вы увидите, что добавьте LIMIT 1 к запросу, и я не знаю, что вы можете сделать что-то лучше, чем это. Однако внутренне __nonzero__ в QuerySet реализован как try: iter(self).next() except StopIteration: return false... поэтому он не исключает исключения.
  • 0
    @Ben: QuerySet.__nonzero__() никогда не вызывается, поскольку QuerySet преобразуется в list перед проверкой на достоверность. Однако могут возникнуть и другие исключения.
Показать ещё 5 комментариев
13

Теперь, в Django 1.9 у вас есть метод first() для запросов.

YourModel.objects.all().first()

Это лучший способ, чем .get() или [0], потому что он не генерирует исключение, если набор запросов пуст. Для этого вам не нужно проверять с помощью exists()

  • 0
    Это вызывает LIMIT 1 в SQL, и я видел утверждения, что это может сделать запрос медленнее - хотя я хотел бы видеть это обоснованным: если запрос возвращает только один элемент, почему LIMIT 1 действительно влияет на производительность? Так что я думаю, что приведенный выше ответ - это хорошо, но хотелось бы, чтобы доказательства подтвердились
7

Если вы планируете часто получать первый элемент - вы можете расширить 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()
  • 0
    Вызовите объекты = ManagerWithFirstQuery как объекты = ManagerWithFirstQuery () - НЕ ЗАБЫВАЙТЕ РОДИТЕЛЯ - в любом случае, вы мне помогли +1
4

Это может быть как

obj = model.objects.filter(id=emp_id)[0]

или

obj = model.objects.latest('id')
3

Вы должны использовать методы django, например, существующие. Его там для вас, чтобы использовать его.

if qs.exists():
    return qs[0]
return None
  • 1
    За исключением того, что, если я правильно понимаю, идиоматический Python обычно использует подход « Проще просить прощения, чем разрешения» ( EAFP ), а не подход « Взгляд перед прыжком» .
  • 0
    EAFP - это не просто рекомендация по стилю, у него есть причины (например, проверка перед открытием файла не предотвращает ошибок). Здесь я думаю, что уместно учитывать, что существует + получить элемент вызывают два запроса к базе данных, что может быть нежелательным в зависимости от проекта и представления.
2

Это может сработать:

def get_first_element(MyModel):
    my_query = MyModel.objects.all()
    return my_query[:1]

если он пуст, затем возвращает пустой список, иначе он возвращает первый элемент внутри списка.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню