Найшвидший спосіб отримати перший об’єкт із набору запитів у 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.