我有一个基本的模型:
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做到这一点?
答案 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
,然后将其称为一天。成本显而易见 - 需要更多的存储空间。关于为什么在同一模型上有Date
和DateTime
字段,这也是非直观的。保持两者同步也可能带来问题。
答案 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。)