Mam model przedstawiający obrazy, które prezentuję na mojej stronie. Na głównej stronie chciałbym pokazać kilka z nich: najnowsze, najczęściej nie odwiedzane, najpopularniejsze i przypadkowe.
Używam Django 1.0.2.
Podczas gdy pierwsze 3 z nich są łatwe do ściągnięcia za pomocą modeli django, ostatnia (losowa) sprawia mi trochę problemów. Moim zdaniem mogę to ofc zakodować na coś takiego:
number_of_records = models.Painting.objects.count()
random_index = int(random.random()*number_of_records)+1
random_paint = models.Painting.get(pk = random_index)
Nie wygląda to na coś, co chciałbym mieć w mojej opinii - jest to całkowicie część abstrakcji bazy danych i powinno znajdować się w modelu. Również tutaj muszę zająć się usuniętymi rekordami (wtedy liczba wszystkich rekordów nie obejmie wszystkich możliwych wartości kluczowych) i prawdopodobnie wiele innych rzeczy.
Jakieś inne opcje, jak mogę to zrobić, najlepiej jakoś wewnątrz abstrakcji modelu?
źródło
Odpowiedzi:
Użycie
order_by('?')
spowoduje zabicie serwera db drugiego dnia produkcji. Lepszym sposobem jest coś, co opisano w sekcji Pobieranie losowego wiersza z relacyjnej bazy danych .źródło
model.objects.aggregate(count=Count('id'))['count']
ponadmodel.objects.all().count()
.all()[randint(0, count - 1)]
w efekcie wykorzystują . Może powinieneś skupić się na określeniu, która część odpowiedzi jest błędna lub słaba, zamiast na nowo definiować dla nas „jeden błąd” i krzyczeć na głupich wyborców. (Może to dlatego, że nie używa.objects
?)Po prostu użyj:
Jest to udokumentowane w QuerySet API .
źródło
random.choice(Model.objects.all())
?Rozwiązania z order_by ('?') [: N] są ekstremalnie powolne, nawet dla tabel średniej wielkości, jeśli używasz MySQL (nie wiem o innych bazach danych).
order_by('?')[:N]
zostaną przetłumaczone naSELECT ... FROM ... WHERE ... ORDER BY RAND() LIMIT N
zapytanie.Oznacza to, że dla każdego wiersza w tabeli zostanie wykonana funkcja RAND (), a następnie cała tabela zostanie posortowana według wartości tej funkcji i zostanie zwróconych pierwszych N rekordów. Jeśli twoje stoły są małe, to w porządku. Ale w większości przypadków jest to bardzo powolne zapytanie.
Napisałem prostą funkcję, która działa nawet jeśli id mają dziury (niektóre wiersze zostały usunięte):
W prawie wszystkich przypadkach jest szybszy niż order_by („?”).
źródło
Oto proste rozwiązanie:
źródło
Możesz stworzyć menedżera na swoim modelu, aby robić tego typu rzeczy. Aby najpierw zrozumieć, co menedżer to, że
Painting.objects
metoda jest menedżerem, który zawieraall()
,filter()
,get()
, itd. Tworzenie własnego menedżera pozwala na filtr wstępny wyniki i mają wszystkie te same metody, jak również własne metody niestandardowe prace nad wynikami .EDYCJA : zmodyfikowałem mój kod, aby odzwierciedlić
order_by['?']
metodę. Zwróć uwagę, że menedżer zwraca nieograniczoną liczbę losowych modeli. Z tego powodu dołączyłem trochę kodu użycia, aby pokazać, jak uzyskać tylko jeden model.Stosowanie
Wreszcie, możesz mieć wielu menedżerów w swoich modelach, więc możesz utworzyć
LeastViewsManager()
lubMostPopularManager()
.źródło
Pozostałe odpowiedzi są albo potencjalnie wolne (używają
order_by('?')
), albo używają więcej niż jednego zapytania SQL. Oto przykładowe rozwiązanie bez porządkowania i tylko z jednym zapytaniem (zakładając Postgres):Należy pamiętać, że spowoduje to błąd indeksu, jeśli tabela jest pusta. Napisz sobie funkcję pomocniczą niezależną od modelu, aby to sprawdzić.
źródło
count()
wyprzedzeniem i zrezygnować z nieprzetworzonego zapytania.Prosty pomysł, jak to robię:
źródło
Wystarczy zwrócić uwagę na (dość powszechny) przypadek specjalny, jeśli w tabeli znajduje się zindeksowana kolumna z automatyczną inkrementacją bez usuwania, optymalnym sposobem wykonania losowego wyboru jest zapytanie takie jak:
która zakłada taką kolumnę o nazwie id dla tabeli. W django możesz to zrobić poprzez:
w którym musisz zastąpić appname nazwą swojej aplikacji.
Ogólnie rzecz biorąc, z kolumną id, order_by („?”) Można zrobić znacznie szybciej za pomocą:
źródło
Jest to wysoce zalecane
Pobieranie losowego wiersza z relacyjnej bazy danychPonieważ użycie django orm do zrobienia czegoś takiego wkurzy twój serwer db, szczególnie jeśli masz tabelę dużych zbiorów danych: |
Rozwiązaniem jest udostępnienie Model Managera i ręczne napisanie zapytania SQL;)
Aktualizacja :
Kolejne rozwiązanie, które działa na dowolnej bazie danych, nawet nie-relacyjnej, bez pisania niestandardowego
ModelManager
. Pobieranie losowych obiektów z Queryset w Djangoźródło
Możesz chcieć użyć tego samego podejścia , którego używałbyś do próbkowania dowolnego iteratora, zwłaszcza jeśli planujesz próbkować wiele elementów, aby utworzyć zestaw próbek . @MatijnPieters i @DzinX dużo przemyśleli:
źródło
OFFSET
), jest to niepotrzebnie nieefektywne.O wiele łatwiejsze podejście polega po prostu na przefiltrowaniu do interesującego nas zestawu rekordów i użyciu
random.sample
do wybrania tylu, ile chcesz:Zauważ, że powinieneś mieć na miejscu kod, aby sprawdzić, czy
my_queryset
nie jest pusty;random.sample
zwraca,ValueError: sample larger than population
jeśli pierwszy argument zawiera zbyt mało elementów.źródło
Queryset
(przynajmniej z Pythonem 3.7 i Django 2.1); musisz najpierw przekonwertować go na listę, która oczywiście pobiera cały zestaw zapytań.Cześć, potrzebowałem wybrać losowy rekord z zestawu zapytań, którego długość również musiałem zgłosić (tj. Strona internetowa wyprodukowała opisaną pozycję i wspomniane rekordy pozostały)
trwało o połowę krócej (0,7 s vs 1,7 s) niż:
Domyślam się, że pozwala to uniknąć ściągnięcia całego zapytania przed wybraniem losowego wpisu i sprawiło, że mój system był wystarczająco responsywny, aby strona była wielokrotnie odwiedzana w celu wykonania powtarzalnego zadania, w którym użytkownicy chcą zobaczyć odliczanie item_count.
źródło
Metoda automatycznego zwiększania wartości klucza podstawowego bez usuwania
Jeśli masz tabelę, w której klucz podstawowy jest sekwencyjną liczbą całkowitą bez przerw, następująca metoda powinna działać:
Ta metoda jest znacznie wydajniejsza niż inne metody, które wykonują iterację we wszystkich wierszach tabeli. Chociaż wymaga dwóch zapytań do bazy danych, oba są trywialne. Ponadto jest to proste i nie wymaga definiowania żadnych dodatkowych klas. Jednak jego zastosowanie jest ograniczone do tabel z automatycznie zwiększającym się kluczem podstawowym, w których wiersze nigdy nie zostały usunięte, tak że nie ma przerw w sekwencji identyfikatorów.
W przypadku, gdy wiersze zostały usunięte, takie jak luki, ta metoda może nadal działać, jeśli zostanie ponowiona, dopóki istniejący klucz podstawowy nie zostanie losowo wybrany.
Bibliografia
źródło
Mam bardzo proste rozwiązanie, wykonaj custom managera:
a następnie dodaj model:
Teraz możesz go użyć:
źródło
order_by('?').first()
ponad 60 razy.