每天使用.latest()的QuerySet

时间:2013-06-20 11:55:06

标签: django

我有一个基本的模型:

class Stats(models.Model):

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

我每10分钟运行一次芹菜作业来创建一个新的统计对象。

.latest()上使用QuerySet为我提供了迄今为止最新的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中,我每天都会使用max来启动一个子查询并将它们连接在一起。

由于我不想使用原始SQL,有没有办法用django ORM做到这一点?

4 个答案:

答案 0 :(得分: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字段,这也是非直观的。保持两者同步也可能带来问题。

答案 1 :(得分:1)

TruncDate在Django&gt; 2.0中是新的,它现在可以缩短相同的查询,但仅限于支持distinct的数据库,如PostgreSQL。

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

答案 2 :(得分: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()

答案 3 :(得分: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。)