2013-06-20 10 views
8

mam Podstawowy model jak:QuerySet z .latest() za każdy dzień

class Stats(models.Model): 

    created = models.DateTimeField(auto_now_add=True) 
    growth = models.IntegerField() 

biegnę pracę seler co 10 minut, aby utworzyć nowy obiekt statystyki.

Za pomocą .latest() na QuerySet otrzymałem najnowszy obiekt Stats do tej pory.

Jednak chcę mieć listę z jednym obiektem Stats dla każdego dnia.

Rozważmy następujący:

Stats(growth=100) #created 1/1/13 23:50 
Stats(growth=200) #created 1/1/13 23:59 
Stats(growth=111) #created 1/2/13 23:50 
Stats(growth=222) #created 1/2/13 23:59 

QuerySet powinien wrócić najpóźniej za każdy dzień. W tym przykładzie o 200 i 222 wzrostu.

W SQL odpaliłbym podzapytanie z maksimum na każdy dzień i dołączałem do niego.

Ponieważ nie chcę używać surowego kodu SQL, czy można to zrobić za pomocą ORM django?

+1

Tylko po to, aby to wyjaśnić w mojej głowie; jeśli chcesz najnowszego na każdy dzień - w twoim przykładzie nie chcesz wzrostu 200 i 222? – Ewan

+0

Tak, zgadza się. Poprawiłem to;) – Jannis

Odpowiedz

4

Niestety nie ma mowy (że jestem świadoma .. Spojrzałem dość trudne), aby uniknąć korzystania niektóre rodzaj surowego sql, aby osiągnąć to, co chcesz zrobić, (z bieżącym modelem, patrz sam koniec dla innej sugestii). Ale możesz zminimalizować wpływ, pisząc tak mało surowego sql, jak to możliwe. W praktyce witryny django nie muszą być przenośne w różnych bazach danych. Jeśli nie planujesz używać tej aplikacji w innym miejscu lub nie publikujesz jej publicznie, powinieneś być w porządku.

Poniższy przykład dotyczy sqlite. Możesz może zachować mapowanie typów baz danych na funkcje date, sprawdzić typ sterownika i zastąpić funkcję prawidłową, jeśli jest to konieczne.

>>> for stat in Stats.objects.all(): 
...  print stat.created, stat.growth 
... 
2013-06-22 13:41:25.334262+00:00 3 
2013-06-22 13:41:40.473373+00:00 3 
2013-06-22 13:41:44.921247+00:00 4 
2013-06-22 13:41:47.533102+00:00 5 
2013-06-23 13:41:58.458250+00:00 6 
2013-06-23 13:42:01.282702+00:00 3 
2013-06-23 13:42:03.633236+00:00 1 

>>> last_stat_per_day = Stats.objects.extra( 
      select={'the_date': 'date(created)' } 
     ).values_list('the_date').annotate(max_date=Max('created')) 

>>> last_stat_per_day 
[(u'2013-06-22', datetime.datetime(2013, 6, 22, 13, 41, 47, 533102, tzinfo=<UTC>)), (u'2013-06-23', datetime.datetime(2013, 6, 23, 13, 42, 3, 633236, tzinfo=<UTC>))] 

>>> max_dates = [item[1] for item in last_stat_per_day] 
>>> max_dates 
[datetime.datetime(2013, 6, 22, 13, 41, 47, 533102, tzinfo=<UTC>), 
datetime.datetime(2013, 6, 23, 13, 42, 3, 633236, tzinfo=<UTC>)] 

>>> stats = Stats.objects.filter(created__in=max_dates) 
>>> for stat in stats: 
...  print stat.created, stat.growth 
... 
2013-06-22 13:41:47.533102+00:00 5 
2013-06-23 13:42:03.633236+00:00 1 

pisałem tu wcześniej, że było to tylko jedno zapytanie ale skłamałam - w values_list musi zostać przekształcone tylko zwrócić max_date dla kolejnych zapytań, co oznacza, że ​​działa oświadczenie. To tylko 2 zapytania, które byłyby znacznie lepsze niż funkcja N + 1.

Non-przenośny nieco to:

last_stat_per_day = Stats.objects.extra( 
    select={'the_date': 'date(created)' } 
).values_list('the_date').annotate(max_date=Max('created')) 

Korzystanie extra nie jest idealna, ale surowego sql tutaj jest prosta i nadaje się dobrze do zależnego wymianie sterownika bazy danych. Tylko date(created) wymaga wymiany. Jeśli chcesz, możesz zawrzeć to w metodzie na menedżerze niestandardowym, a następnie z powodzeniem wyodrębnić ten bałagan w jednym miejscu.

Inną opcją jest po prostu dodanie modelu DateField, a następnie nie trzeba w ogóle używać więcej. Po prostu zamienisz wywołanie values_list na values_list('created_date'), całkowicie usuniesz extra i nazwij to dziennie. Koszt jest oczywisty - potrzeba więcej miejsca.Jest również nieintuicyjne pytanie o to, dlaczego masz pole Date i DateTime w tym samym modelu. Utrzymywanie synchronizacji dwóch osób może również stwarzać problemy.

0

Może można zrobić somehting jak:

import datetime 
day = datetime.datetime.now().day 
the_last_one = Stats.objects.filter(created__day=day).order_by('-created')[0] 

lub coś

the_last_one = Stats.objects.filter(created__day=day).order_by('created').latest() 
+0

będą zwracać najnowszy obiekt Stats, a nie listę obiektów z najnowszymi statystykami dla każdego dnia. – Jannis

0

Oprócz dwóch pozostałych odpowiedzi, może również rozważyć zapisanie wyników w innym modelu (zwłaszcza jeśli dane dziennie nie zmieniają się zbytnio po wprowadzeniu i masz duże ilości danych). Coś jak:

class DailyStat(models.Model): 
    date = models.DateField(unique=True) 
    # Denormalisation yo 
    # Could also store foreign keys to Stats instances if needed 
    max_growth = models.IntegerField() 
    min_growth = models.IntegerField() 
    # . 
    # . 
    # . 
    # and any other stats per day e.g. average per day 

i dodać okresowe zadanie Seler:

from celery.task.schedules import crontab 
from celery.task import periodic_task 
import datetime 

# Periodic task for 1am daily 
@periodic_task(run_every=crontab(minute=0, hour=1)) 
def process_stats_ery_day(): 
    # Code to populate DailyStat 
    today = datetime.date.today() 
    # Assumes relevant custom Manager methods exist 
    # Can use regular Django ORM methods to achieve this 
    max = Stats.objects.get_max_growth(date=today) 
    min = Stats.objects.get_min_growth(date=today) 
    ds = DailyStat(date=today, max_growth=max.growth, min_growth=min.growth) 
    ds.save() 

Wyświetl wyniki z:

DailyStat.objects.all() 

oczywiście oprócz innych czynników do rozważenia, to podejście przedstawia kwestię konieczności aktualizowania DailyStat, gdy przeszłe statystyki zmieniają się i zmieniają (signals mogą być użyte, jeśli weźmiesz tę ścieżkę.)

0

TruncDate jest nowością w Django> 2.0 i jest teraz możliwe skrócenie tego samego zapytania, ale tylko w bazach danych z obsługą distinct podobną do PostgreSQL.

Stats.objects.all().annotate(date=TruncDay('created')).distinct('created').order_by('-date')