Django - ograniczenie wyników zapytań

200

Chcę wziąć ostatnie 10 instancji modelu i mieć ten kod:

 Model.objects.all().order_by('-id')[:10]

Czy to prawda, że ​​najpierw zbieraj wszystkie wystąpienia, a następnie weź tylko 10 ostatnich? Czy istnieje bardziej skuteczna metoda?

krzyhub
źródło

Odpowiedzi:

304

Zestawy zapytań Django są leniwe. Oznacza to, że zapytanie trafi do bazy danych tylko wtedy, gdy poprosisz o wynik.

Więc dopóki nie wydrukujesz lub nie użyjesz wyniku zapytania, możesz filtrować dalej bez dostępu do bazy danych.

Jak widać poniżej, kod wykonuje tylko jedno zapytanie SQL, aby pobrać tylko 10 ostatnich elementów.

In [19]: import logging                                 
In [20]: l = logging.getLogger('django.db.backends')    
In [21]: l.setLevel(logging.DEBUG)                      
In [22]: l.addHandler(logging.StreamHandler())      
In [23]: User.objects.all().order_by('-id')[:10]          
(0.000) SELECT "auth_user"."id", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."password", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."is_superuser", "auth_user"."last_login", "auth_user"."date_joined" FROM "auth_user" ORDER BY "auth_user"."id" DESC LIMIT 10; args=()
Out[23]: [<User: hamdi>]
hamdiakoguz
źródło
Próbowałem tego na mongoDB i napisano, że SELECT nie jest obsługiwane. Jak to zrobić na mongoDB?
winux
@winux Ponieważ jest to specyficzne dla Django, wygląda na to, że możesz potrzebować skonfigurować Django do pracy z bazami danych typu Mongo / NoSQL. Z mojego doświadczenia nie jest to typowa konfiguracja w odniesieniu do standardowej konfiguracji Django ORM.
anonimowy tchórz
38

Właściwie myślę, LIMIT 10że zostaną wydane do bazy danych, więc krojenie nie nastąpi w Pythonie, ale w bazie danych.

Aby uzyskać więcej informacji, zobacz zestawy zapytań ograniczających .

Davor Lucic
źródło
Pamiętaj, że to nie zadziała w przypadku zestawów zapytań, które również wymagają filtrowania, ponieważ nie można filtrować po krojeniu.
Mike 'Pomax' Kamermans
2
Więc przefiltruj najpierw niż pokrój go. Dzięki Davor za link!
Vyachez
13

Wygląda na to, że rozwiązanie w pytaniu nie działa już w Django 1.7 i pojawia się błąd: „Nie można zmienić kolejności zapytania po pobraniu plastra”

Zgodnie z dokumentacją https://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets wymuszając parametr „step” składni plastra Python ocenia zapytanie. Działa w ten sposób:

Model.objects.all().order_by('-id')[:10:1]

Nadal zastanawiam się, czy limit jest wykonywany w SQL, czy w Pythonie wycina całą tablicę wyników. Nie ma potrzeby pobierania ogromnych list do pamięci aplikacji.

Nikolay Grischenko
źródło
Nawet to rozwiązanie nie działa z testowanym django> = 1.8.
sonus21
3

Tak. Jeśli chcesz pobrać ograniczony podzbiór obiektów, możesz użyć poniższego kodu:

Przykład:

obj=emp.objects.all()[0:10]

Początkowe 0 jest opcjonalne, więc

obj=emp.objects.all()[:10]

Powyższy kod zwraca pierwsze 10 wystąpień.

patel shahrukh
źródło
1

Jako uzupełnienie i spostrzeżenie do innych użytecznych odpowiedzi, warto zauważyć, że faktyczne wykonanie [:10]wycinania zwróci pierwsze 10 elementów listy , a nie ostatnie 10 ...

Aby zdobyć ostatnie 10, powinieneś zrobić [-10:]zamiast tego (patrz tutaj ). Pomoże to uniknąć korzystania order_by('-id')z przy -odwracaniu elementów.

DarkCygnus
źródło
1
Próbowałem tego i otrzymałem „Negatywne indeksowanie nie jest obsługiwane”.
bparker
@DarkCygnus Product.objects.filter(~Q(price=0))[-5:]powoduje ten sam błąd: „Indeksowanie ujemne nie jest obsługiwane”.
bersam
To nie działa w django na zestawie zapytań: code.djangoproject.com/ticket/13089 Jeśli przekonwertujesz zestaw zapytań na listę, zadziała.
valem