Найшвидший спосіб отримати перший об’єкт із набору запитів у django?


193

Часто мені здається, що я хочу отримати перший об’єкт із набору запитів у 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.

Як я можу це зробити лише за допомогою одного дзвінка до бази даних та без виклику пам'яті з об'єктами виключення?


21
Правило: Якщо ви турбуєтеся про мінімізацію DB спуско-, не використовуйте len()на querysets, завжди використовуйте .count().
Даніель Діпаоло,

7
"Створення об’єкта винятку багато часу, що дуже затягує пам'ять" - якщо ви стурбовані створенням додаткових винятків, ви робите це неправильно, оскільки Python використовує винятки в усьому світі. Ви насправді визначили, що у вашому випадку це об'єм пам'яті?
lqc

1
@Leopd І якщо ви насправді будь-яким чином (або принаймні коментарі) ви орієнтували показника, то знаєте, що це не швидше. Насправді це може бути повільніше, тому що ви створюєте додатковий список, щоб просто викинути його. І все це просто арахіс порівняно з вартістю виклику функції python або використання ORM Django в першу чергу! Один виклик фільтрувати () багато, багато, багато разів повільніше, ніж підняти виняток (який все одно буде піднято, тому що так працює ітераторський протокол!).
lqc

1
Ваша інтуїція правильна, що різниця у виконанні невелика, але ваш висновок неправильний. Я запустив орієнтир, і прийнята відповідь насправді швидша за реальний запас. Піди розберися.
Леопд

11
Для людей , що використовують Django 1.6, вони нарешті -то додали first()і last()зручність методи: docs.djangoproject.com/en/dev/ref/models/querysets/#first
Wei Yen

Відповіді:


328

Django 1.6 (випущений листопад 2013) представив зручні методи first() і last()які ковтають вийшло виняток і повернення , Noneякщо QuerySet не повертає об'єктів.


2
він не робить [: 1], тому він не такий швидкий (якщо ви все одно не повинні оцінювати весь набір запитів).
janek37

13
також, first()і last()застосувати ORDER BYпункт про запит. Це зробить результати детермінованими, але, швидше за все, сповільнить запит.
Філ Крилов

@ janek37 різниці в продуктивності. Як вказує cod3monk3y, це зручний метод, і він не читає весь набір запитів.
Zompa

143

Правильна відповідь

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

Які можна використовувати в:

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

Спочатку ви не хочете перетворювати його на список, оскільки це змусить виклик повної бази даних усіх записів. Просто зробіть вищесказане, і це потягне лише перше. Ви навіть можете використати .order_byдля того, щоб отримати перше, що хочете.

Не забудьте додати, .get()інакше ви отримаєте QuerySet назад, а не об’єкт.


9
Вам все одно доведеться завершити це в спробу ... крім ObjectDoesNotExist, який схожий на початковий третій варіант, але з нарізкою.
Danny W. Adair

1
Який сенс встановити ліміт, якщо ви зрештою подзвоните get ()? Нехай ORM і компілятор SQL вирішують, що найкраще для цього бекенда (наприклад, у Oracle Django емулює LIMIT, тому це зашкодить замість допомоги).
lqc

Я використав цю відповідь без останнього .get (). Якщо список повертається, я повертаю перший елемент списку.
Кіт Джон Хатчісон

чим відрізняється наявність Entry.objects.all()[0]??
Джеймс Лін

15
@JamesLin Різниця полягає в тому, що [: 1] .get () підвищує DoesNotExist, тоді як [0] підвищує IndexError.
Ropez

49
r = list(qs[:1])
if r:
  return r[0]
return None

1
Якщо ви ввімкнете трасування, я впевнений, що ви навіть побачите це додавання LIMIT 1до запиту, і я не знаю, що ви можете зробити краще, ніж це. Однак, внутрішня програма __nonzero__в QuerySetреалізована try: iter(self).next() except StopIteration: return false...так, що не уникає винятку.
Бен Джексон

@Ben: QuerySet.__nonzero__()ніколи не викликається, оскільки QuerySetперетворюється на a, listперш ніж перевіряти справжність . Однак інші винятки все ж можуть бути.
Ігнасіо Васкес-Абрамс

@Aron: Це може створити StopIterationвиняток.
Ігнасіо Васкес-Абрамс

перетворення в список === виклик __iter__для отримання нового об'єкта ітератора та виклик його nextметоду, поки не StopIterationбуде кинуто. Тож остаточно десь буде виняток;)
lqc

14
Ця відповідь застаріла, подивіться на @ cod3monk3y відповідь для Django 1.6+
ValAyal

37

Тепер у Django 1.9 у вас є first() метод для наборів запитів.

YourModel.objects.all().first()

Це кращий спосіб, ніж .get()чи [0]тому, що він не кидає виняток, якщо набір запитів порожній. Тому, вам не потрібно перевіряти використанняexists()


1
Це спричиняє обмеження 1 у SQL, і я бачив твердження, що він може робити запит повільніше - хоча я хотів би бачити це обґрунтованим: Якщо запит повертає лише один елемент, то чому LIMIT 1 дійсно впливає на продуктивність? Тому я вважаю, що вищезазначена відповідь чудова, але я хотів би бачити докази, що підтверджують.
rrauenza

Я б не сказав "краще". Це дійсно залежить від ваших очікувань.
триграс

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()

Викличте об'єкти = ManagerWithFirstQuery як об’єкти = ManagerWithFirstQuery () - НЕ ЗАБУДУЙТЕ ЗАБАВИТИ РОБИТЬ - все одно, ви мені так допомогли +1
Каміль

7

Це може також працювати:

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

якщо він порожній, то повертає порожній список, інакше він повертає перший елемент всередині списку.


1
Це, безумовно , краще рішення ... призводить тільки один виклик до бази даних
Тсс


3

Ви повинні використовувати методи джанго, як це існує. Його ви там можете використовувати.

if qs.exists():
    return qs[0]
return None

1
За винятком випадків, якщо я так правильно розумію, ідіоматичний Python, як правило, використовує простіший запит прощення, ніж дозвіл ( EAFP ), а не підхід, який дивиться перед вашим стрибком .
BigSmoke

EAFP - це не просто рекомендація щодо стилю, вона має причини (наприклад, перевірка перед відкриттям файлу не запобігає помилкам). Тут я думаю, що відповідне враження полягає в тому, що існує + отримати пункт викликає два запити до бази даних, які можуть бути небажаними залежно від проекту та перегляду.
Ерік Арауджо

2

Оскільки django 1.6 ви можете використовувати filter () за допомогою першого методу () таким чином:

Model.objects.filter(field_name=some_param).first()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.