Najszybszy sposób na uzyskanie pierwszego obiektu z zestawu zapytań w Django?


193

Często mam ochotę pobrać pierwszy obiekt z zestawu zapytań w Django lub zwrócić, Nonejeśli nie ma żadnych. Istnieje wiele sposobów na zrobienie tego, które wszystkie działają. Ale zastanawiam się, który jest najbardziej wydajny.

qs = MyModel.objects.filter(blah = blah)
if qs.count() > 0:
    return qs[0]
else:
    return None

Czy to powoduje dwa wywołania bazy danych? To wydaje się marnotrawstwem. Czy to jest szybsze?

qs = MyModel.objects.filter(blah = blah)
if len(qs) > 0:
    return qs[0]
else:
    return None

Inną opcją byłoby:

qs = MyModel.objects.filter(blah = blah)
try:
    return qs[0]
except IndexError:
    return None

Generuje to pojedyncze wywołanie bazy danych, co jest dobre. Wymaga to jednak częstego tworzenia obiektu wyjątku, co wymaga dużej ilości pamięci, gdy wszystko, czego naprawdę potrzebujesz, to banalny test wstępny.

Jak mogę to zrobić za pomocą jednego wywołania bazy danych i bez utraty pamięci z obiektami wyjątków?


21
len()Ogólna zasada: jeśli martwisz się minimalizacją zwrotów bazy danych w obie strony, nie używaj zestawu zapytań, zawsze używaj .count().
Daniel DiPaolo,

7
„często tworzy się obiekt wyjątku, co wymaga dużej ilości pamięci” - jeśli martwisz się utworzeniem jednego dodatkowego wyjątku, robisz to źle, ponieważ Python używa wyjątków wszędzie. Czy faktycznie porównałeś, że w twoim przypadku jest to dużo pamięci?
lqc

1
@Leopd A gdybyś rzeczywiście przetestował anwser w jakikolwiek sposób (a przynajmniej komentarze), wiedziałbyś, że nie będzie to szybsze. W rzeczywistości może być wolniejszy, ponieważ tworzysz dodatkową listę, aby ją wyrzucić. A wszystko to tylko orzeszki ziemne w porównaniu z kosztem wywołania funkcji python lub użycia ORM Django w pierwszej kolejności! Pojedyncze wywołanie filter () jest wiele, wiele, wiele razy wolniejsze niż zgłoszenie wyjątku (który wciąż będzie zgłaszany, bo tak działa protokół iteratora!).
lqc

1
Twoja intuicja jest słuszna, że ​​różnica w wydajności jest niewielka, ale twój wniosek jest błędny. Przeprowadziłem test porównawczy, a zaakceptowana odpowiedź jest w rzeczywistości szybsza o realny margines. Domyśl.
Leopd,

11
Dla ludzi korzystających z Django 1.6, oni wreszcie dodał first()i last()wygodę metod: docs.djangoproject.com/en/dev/ref/models/querysets/#first
Wei Yen

Odpowiedzi:


328

Django 1,6 (wydany listopad 2013) wprowadził metody wygodę first() i last()który połknąć otrzymanego wyjątku i powrotu Nonejeżeli nie zwróci queryset obiektów.


1
nie robi [: 1], więc nie jest tak szybki (chyba że i tak musisz ocenić cały zestaw zapytań).
janek37

13
Również first()i last()egzekwowanie ORDER BYklauzuli o zapytaniu. Sprawi, że wyniki będą deterministyczne, ale najprawdopodobniej spowolni zapytanie.
Phil Krylov

@ janek37 nie ma różnic w wydajności. Jak wskazuje cod3monk3y, jest to wygodna metoda i nie odczytuje całego zestawu zapytań.
Zompa,

142

Poprawna odpowiedź to

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

Które mogą być użyte w:

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

Nie chciałbyś najpierw przekształcić go w listę, ponieważ wymusiłoby to pełne wywołanie bazy danych wszystkich rekordów. Po prostu zrób powyższe, a pociągnie tylko pierwszy. Możesz nawet użyć, .order_byaby upewnić się, że otrzymujesz pierwszy, czego chcesz.

Pamiętaj, aby dodać .get()lub otrzymasz QuerySet, a nie obiekt.


9
Nadal będziesz musiał go zawinąć przy próbie ... oprócz ObjectDoesNotExist, która jest jak oryginalna trzecia opcja, ale z krojeniem.
Danny W. Adair,

1
Po co ustawiać LIMIT, jeśli w końcu wywołasz get ()? Pozwól ORM i kompilatorowi SQL zdecydować, co jest najlepsze dla jego backendu (na przykład w Oracle Django emuluje LIMIT, więc będzie bolało zamiast pomagać).
lqc

Użyłem tej odpowiedzi bez końcowego .get (). Jeśli lista jest zwracana, zwracam pierwszy element listy.
Keith John Hutchison,

co różni się od posiadania Entry.objects.all()[0]?
James Lin

15
@JamesLin Różnica polega na tym, że [: 1] .get () podnosi DoesNotExist, a [0] podnosi IndexError.
Ropez

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

1
Jeśli włączysz śledzenie, jestem prawie pewien, że zobaczysz ten dodatek LIMIT 1do zapytania i nie wiem, czy możesz zrobić coś lepszego niż to. Jednak wewnętrznie __nonzero__w QuerySetjest zaimplementowane, ponieważ try: iter(self).next() except StopIteration: return false...nie ucieka od wyjątku.
Ben Jackson

@Ben: QuerySet.__nonzero__()nigdy nie jest wywoływany, ponieważ QuerySetjest listsprawdzany przed sprawdzeniem poprawności. Jednak nadal mogą wystąpić inne wyjątki.
Ignacio Vazquez-Abrams

@Aron: Może wygenerować StopIterationwyjątek.
Ignacio Vazquez-Abrams,

konwersja do list === wywołanie, __iter__aby uzyskać nowy obiekt iteratora i wywołać jego nextmetodę doStopIteration wyrzucenia. Tak więc na pewno będzie gdzieś wyjątek;)
lqc

14
Ta odpowiedź jest teraz nieaktualna, spójrz na odpowiedź @ cod3monk3y dla Django 1.6+
1.6+

37

Teraz w Django 1.9 masz first() metodę zestawów zapytań.

YourModel.objects.all().first()

Jest to lepszy sposób niż .get()lub [0]dlatego, że nie zgłasza wyjątku, jeśli zestaw zapytań jest pusty. Therafore, nie trzeba sprawdzać za pomocąexists()


1
Powoduje to LIMIT 1 w SQL i widziałem twierdzenia, że ​​może to spowolnić zapytanie - chociaż chciałbym, aby było to uzasadnione: Jeśli zapytanie zwraca tylko jeden element, dlaczego LIMIT 1 naprawdę powinien wpływać na wydajność? Myślę więc, że powyższa odpowiedź jest dobra, ale chciałbym zobaczyć dowody potwierdzające.
rrauenza

Nie powiedziałbym „lepiej”. To naprawdę zależy od twoich oczekiwań.
Trigras

7

Jeśli planujesz często zdobywać pierwszy element - możesz rozszerzyć QuerySet w tym kierunku:

class FirstQuerySet(models.query.QuerySet):
    def first(self):
        return self[0]


class ManagerWithFirstQuery(models.Manager):
    def get_query_set(self):
        return FirstQuerySet(self.model)

Zdefiniuj model w następujący sposób:

class MyModel(models.Model):
    objects = ManagerWithFirstQuery()

I użyj tego w ten sposób:

 first_object = MyModel.objects.filter(x=100).first()

Wywołaj obiekty = ManagerWithFirstQuery jako obiekty = ManagerWithFirstQuery () - NIE ZAPOMNIJ RODZICÓW - zresztą pomogłeś mi więc +1
Kamil

7

Może to również działać:

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

jeśli jest pusty, to zwraca pustą listę, w przeciwnym razie zwraca pierwszy element na liście.


1
To zdecydowanie najlepsze rozwiązanie ... skutkuje tylko jednym wywołaniem do bazy danych
Cii

5

Może tak być

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

lub

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

3

Powinieneś używać metod django, tak jak istnieje. Możesz go użyć.

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

1
Tyle że, jeśli dobrze to rozumiem, idiomatyczny Python zwykle używa łatwiejszego podejścia do prośby o przebaczenie niż pozwolenie ( EAFP ) niż podejścia typu Look Before You Leap .
BigSmoke

EAFP nie jest tylko zaleceniem stylu, ma powody (na przykład sprawdzenie przed otwarciem pliku nie zapobiega błędom). Tutaj myślę, że istotną kwestią jest to, że istnieje + pobranie elementu powoduje dwa zapytania do bazy danych, które mogą być niepożądane w zależności od projektu i widoku.
Éric Araujo

2

Od wersji django 1.6 możesz używać filter () metodą first () w następujący sposób:

Model.objects.filter(field_name=some_param).first()
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.