2013-06-20 132 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')