2015-09-02 13 views
6

Tu jest moja klasa Django:Jak Django queryset że wybiera rekordy z maksymalnej wartości w grupie

class MyClass(models.Model): 
    my_integer = models.IntegerField() 
    created_ts = models.DateTimeField(default=datetime.utcnow, editable=False) 

chciałbym odzyskać instancje MyClass że masz najnowszą created_ts dla każdej unikatowej wartości my_integer . Nie mogę wymyślić, jak to zrobić.

Czy ktoś może pokazać, jak to zrobić?

+0

"najnowsze" oznacza tylko jeden. – Gocht

+1

"najnowsze created_ts dla każdej unikalnej wartości my_integer". To znaczy więcej niż jeden. –

+1

@SaqibAli Twoje pytanie brzmi "Chciałbym pobrać instancje' MyClass' [...] ", ale zaakceptowałeś odpowiedź, która nie pobiera instancji' MyClass'. Zwraca słowniki. Nawet [skomentowałeś] (http://stackoverflow.com/questions/32359954/how-to-make-django-queryset-that-selects-records-w-max-value-winin-a-group#comment52598332_32361355) na innym odpowiedź, że chcesz instancji klasy. – Louis

Odpowiedz

0

niesprawdzone

results = MyClass.objects.all().distinct('my_integer').order_by('created_ts') 
+0

Dało mi to wyjątek: 'NotImplementedError: DISTINCT ON pola nie są obsługiwane przez ten backend bazy danych'. FYI, używam mysql. –

0
MyClass.objects.order_by('my_integer', '-created_ts').distinct('my_integer') 

Według distinct, trzeba zadzwonić na odrębne cechy, w tej samej kolejności jak w order_by. Stąd porządkuj elementy w oparciu o liczbę całkowitą, a następnie w odwrotną sygnaturę czasową i wywołuj odrębne na nich, które zwraca ostatnią instancję dla każdej liczby całkowitej.

+0

Dało mi to wyjątek: 'NotImplementedError: DISTINCT ON pola nie są obsługiwane przez ten backend bazy danych'. FYI, używam mysql. –

+0

Skorzystaj z tego, http://stackoverflow.com/questions/12402923/django-mysql-distinct-query-for-getting-multiple-values ​​mysql backend nie obsługuje operacji 'distinct()'. Jego odpowiednik (o ile mi wiadomo) 'MyClass.objects.values ​​('my_integer', 'created_ts'). Distinct(). Order_by ('- created_ts')'. Testowane i działa zgodnie z oczekiwaniami. – ianveshi

+0

Dzięki ianveshi. To mnie przybliża. Ale wciąż nie do końca. Muszę uzyskać rzeczywiste instancje MyClass, które spełniają te kryteria. twoje rozwiązanie nadaje wartości 'created_ts' tylko wartość' my_integer', dla której jest poprawna. Musisz założyć, że w rzeczywistości MyClass jest o wiele bardziej złożoną klasą niż po prostu 'my_integer' i' created_ts'. Potrzebuję tego. W jaki sposób? –

0

Spróbuj tego;

from django.db.models import Max 

MyClass.objects.values('my_integer').annotate(Max('created_ts')) 
+0

To daje nam tylko wartości '' my_integer'' i '' created_ts__max'' obiektów, a nie rzeczywiste instancje. – maxsocl

0

To jest naprawdę podstawowy sposób na zrobienie tego. Zakładając, że ilość posiadanych danych nie jest zbyt duża, przyniesie to efekty. Możesz użyć tego w swoich widokach, zastępując funkcję get_queryset i po prostu zwracając filtrowane. Możesz też użyć go jako metody statycznej na swojej klasie, jeśli planujesz używać jej wszędzie.

values = MyClass.objects.order_by('-created_ts').all() 

filtered = [] 
existing = [] 
for value in values: 
    if value.my_integer not in existing: 
     existing.append(value.my_integer) 
     filtered.append(value) 

Ponieważ lista jest uporządkowana według najnowszej wersji, zostaną one dodane do istniejącej pierwszej dla tej liczby całkowitej. Zrobiłem kilka podstawowych testów z tym, ale niewiele, więc może tam być wada lub dwa. Testowany z sqlite.

Edit

Tutaj jest znacznie szybsza wersja.

def iter_tools(): 
    import itertools 
    qs = MyClass.objects.all() 
    filtered = [] 
    group_by = itertools.groupby(qs, lambda x: x.my_integer) 
    for x in group_by: 
     filtered.append(sorted(x[1], key=lambda x: x.created_ts, reverse=True)[0]) 
    return filtered 

Zasadniczo sposób ten działa jest uzyskanie wszystkich obiektów z DB, grupując je przez liczbę całkowitą, a następnie sortowanie każdą grupę na podstawie znacznika czasu i coraz to pierwszy po jednym z każdej grupy. Przyspieszenie tego jeszcze bardziej wykracza poza moje umiejętności, ale jestem pewien, że jest kilka sposobów.

Oto timeit tego jednego vs jednej wcześniejszej ze tylko jak 6 wpisów w dB:

In[]: timeit.timeit(manual, number=1500) 
Out[]: 0.5577559471130371 
In[]: timeit.timeit(iter_tools, number=1500) 
Out[]: 0.39012885093688965 
----------------------------------------------- 
In[]: timeit.timeit(manual, number=5000) 
Out[]: 1.770777940750122 
In[]: timeit.timeit(iter_tools, number=5000) 
Out[]: 1.2411231994628906 

Edit 2: utworzonego 60000 obiektów do bazy danych, aby ją wypróbować niektóre dane. Wygenerowałem dane za pomocą django-fixtureless, więc liczby całkowite są całkowicie losowe, a znacznik czasu na nich wszystkich jest nowy datetime.now() dla każdego obiektu.

In[]: timeit.timeit(manual, number=1) 
Out[]: 11.946185827255249 
In[]: timeit.timeit(iter_tools, number=1) 
Out[]: 0.7811920642852783 
In[]: timeit.timeit(iter_tools, number=100) 
Out[]: 77.93837308883667 
In[]: MyClass.objects.all().count() 
Out[]: 60000 

Uwaga na temat DB: W powyższych przykładach używałem sqlite3 tylko na moim komputerze lokalnym. Właśnie ustawiłem szybki mały serwer mysql jako vm i otrzymałem znacznie lepszy wynik prędkości.

In[16]: MyClass.objects.all().count() 
Out[16]: 60000 
In[17]: timeit.timeit(iter_tools, number=100) 
Out[17]: 49.636733055114746 
In[18]: timeit.timeit(iter_tools, number=1) 
Out[18]: 0.4923059940338135 

Tak czy inaczej, otrzymasz te same przedmioty, które zostały zwrócone.Jeśli wydajność jest problemem, zaleciłbym użycie itertools one lub niestandardowego zapytania sql.

+0

Tak więc sortowanie/grupowanie nie może być wykonywane przez bazę danych za pomocą zestawów zapytań Django? Miałem nadzieję, że nie będę pisał kodu, żeby to zrobić. Doszedłem do wniosku, że DB będzie bardziej wydajny. –

+0

Zobacz moją ostatnią zmianę, zamieściłem kilka razy z prawdziwą bazą danych mysql. Ale aby odpowiedzieć na twoje pytanie, nie jestem świadomy żadnego sposobu, aby zrobić to bezpośrednio z DB bez niestandardowego SQL. Ale to nie znaczy, że nie ma sposobów. Jestem dość niedoświadczony dzięki połączeniu mysql i django, więc może być kilka sposobów na zrobienie tego. Jednak zrobienie tego w prostym kodzie może być łatwiejsze niż znalezienie złożonej ukrytej funkcji django. – electrometro

+0

Nie zapominaj, że możesz również odfiltrować wyniki w zapytaniu, by był bardziej aktualny, jeśli tworzysz te obiekty bardzo często. Przykład: jeśli wiesz, że wszystkie wyniki będą potrzebne w ciągu ostatnich 5 minut, możesz dodać filtr do qs, który znacznie zmniejszy liczbę obiektów, które funkcja musi przechodzić. Prawidłowa odpowiedź będzie w rzeczywistości różna w zależności od wielkości danych, częstotliwości aktualizacji wszystkich danych, częstotliwości obliczania wyników i szybkości ich wykonywania. – electrometro

3

to pomoże

from django.db.models import Count, Max 
MyClass.objects.values('my_integer').annotate(count=Count("my_integer"),latest_date=Max('created_ts')) 

dane w tabeli

my_integer  created_ts 
    -    ----------- 
    1 2015-09-08 20:05:51.144321+00:00 
    1 2015-09-08 20:08:40.687936+00:00 
    3 2015-09-08 20:08:58.472077+00:00 
    2 2015-09-08 20:09:08.493748+00:00 
    2 2015-09-08 20:10:20.906069+00:00 

Wyjście

[ 
    {'count': 2, 'latest_date': datetime.datetime(2015, 9, 8, 20, 8, 40, 687936, tzinfo=<UTC>), 'my_integer': 1}, 
    {'count': 2, 'latest_date': datetime.datetime(2015, 9, 8, 20, 10, 20, 906069, tzinfo=<UTC>), 'my_integer': 2}, 
    {'count': 1, 'latest_date': datetime.datetime(2015, 9, 8, 20, 8, 58, 472077, tzinfo=<UTC>), 'my_integer': 3} 
] 
0

Można też zrobić aR Zapytanie AW:

MyClass.objects.raw(""" 
SELECT m1.id, m1.my_integer, m1.created_ts 
FROM app_myclass AS m1, (
    SELECT my_integer, MAX(created_ts) AS max_created_ts 
    FROM app_myclass 
    GROUP BY my_integer 
) AS m2 
WHERE m1.my_integer = m2.my_integer AND m1.created_ts = m2.max_created_ts 
""")) 

Albo użyć Django ORM:

MyClass.objects.filter(
    created_ts__in=MyClass.objects.values(
     "my_integer" 
    ).annotate(
     created_ts=models.Max(
      "created_ts" 
     ) 
    ).values_list("created_ts", flat=True) 
) 

Zauważ, że wymaga to tylko jedno żądanie SQL, jak widać, drukując len(django.db.connection.queries) przed i po zapytaniu.

Należy jednak zauważyć, że drugie rozwiązanie działa tylko wtedy, gdy atrybut created_ts jest unikalny, co może nie być w danym przypadku.

Jeśli nie chcesz używać surowych zapytań lub indeksu na created_ts, powinieneś prawdopodobnie zacząć używać PostgreSQL i jego funkcji DISTINCT ON, jak sugerują inne odpowiedzi.

Powiązane problemy