2013-06-20 45 views
8

我有這樣一個基本模型:查詢集與.latest()每個

class Stats(models.Model): 

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

我運行芹菜工作每10分鐘創建一個新的統計對象。

QuerySet上使用.latest()爲我提供了迄今爲止最新的Stats對象。

但是,我希望每天有一個Stats對象的列表。

考慮以下幾點:

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應返回最新的每一天。在這個例子中,增長率爲200和222。

在SQL中,我會用每天的最大值啓動一個子查詢並將它們結合在一起。

因爲我不想使用原始SQL,有沒有辦法用django ORM來做到這一點?

+1

只是爲了得到這個清晰的在我的腦海;如果你想要每一天的最新消息 - 在你的榜樣中你會不想要200和222增長? – Ewan

+0

是的,沒錯。我糾正了它;) – Jannis

回答

4

遺憾的是沒有辦法(我所知道的。我看了相當困難),以避免使用一些一種原始的sql來完成你想要做的事(用你目前的模型;看到最後的另一個建議)。但是,您可以儘可能少地寫入原始sql,從而將影響降至最低。在實踐中,Django站點不需要跨不同數據庫移植。除非你打算在別處使用這個應用程序或公開發布它,否則你應該沒問題。

以下示例適用於sqlite。您可以可以保留數據庫類型到date函數的映射,查找驅動程序的類型,並根據需要使用正確的函數替換函數。

>>> 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 

我在這裏寫之前,這只是一個單一的查詢,但我撒謊 - 在values_list需要轉換到只返回MAX_DATE爲連續查詢,這意味着運行的語句。儘管只有2個查詢,但它會比N + 1函數好得多。

非便攜式位是這樣的:

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

使用extra並不理想,但這裏的原始的SQL語句簡單,很好地適合於數據庫驅動程序相關的替代品。只需要更換date(created)。如果你喜歡,你可以用自定義管理器的方法把它包裝起來,然後你可以在一個位置成功地抽象出這個混亂。

另一種方法是隻在模型中添加一個DateField,然後根本不需要額外使用。您只需將values_list呼叫替換爲values_list('created_date'),完全刪除extra,然後每天給它打電話。成本顯而易見 - 需要更多的存儲空間。這對於爲什麼在同一模型上有DateDateTime字段也是非直觀的。保持兩者同步也可能造成問題。

0

也許你能做到像服用點:

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

或類似的東西

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

他們將返回最新的Stats對象,而不是每天有最新統計信息的對象列表。 – Jannis

0

除了其他兩個答案之外,也可能考慮將結果存儲在另一個模型中(特別是如果每​​天的數據在輸入後沒有太大變化並且您有大量數據)。喜歡的東西:

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 

,並添加一個週期性的芹菜任務:

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() 

獲取與結果:

DailyStat.objects.all() 

當然,除其他因素考慮,這種方法存在的問題必須在過去的統計信息發生變化時更新DailyStat(如果您採用此路徑,則可以使用signals)。

0

TruncDate在Django> 2.0中是新增的,現在可以縮短相同的查詢時間,但只適用於像PostgreSQL這樣支持distinct的數據庫。

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