10

我有一个基本模型,例如:

class Stats(models.Model):

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

我每 10 分钟运行一次 celery 作业以创建一个新的 stats 对象。

使用.latest()aQuerySet给了我迄今为止最新的 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 做到这一点?

4

4 回答 4

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)需要更换的。如果愿意,您可以将其包装在自定义管理器上的方法中,然后您已成功地将这些混乱抽象到一个位置。

另一种选择是只将 a 添加DateField到您的模型中,然后您根本不需要使用额外的。您只需将values_list呼叫替换为 a values_list('created_date'),完全删除extra,然后就可以结束了。成本是显而易见的——需要更多的存储空间。至于为什么你在同一个模型上有一个Date和一个DateTime字段也是不直观的。保持两者同步也可能会带来问题。

于 2013-06-23T14:28:07.827 回答
3

TruncDate 是 Django >2.0 中的新功能,现在可以缩短相同的查询,但仅限于distinct支持 PostgreSQL 的数据库中。

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

于 2018-01-17T16:15:27.367 回答
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()
于 2013-06-20T16:56:40.920 回答
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

并添加一个周期性的 Celery 任务:

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 等等(如果您确实采用了这条路径,则可以使用信号。)

于 2013-06-24T02:26:51.890 回答