【问题标题】:Retrieving newest object for persons检索人员的最新对象
【发布时间】:2011-05-25 12:32:12
【问题描述】:

所以,我有一个 Person 模型,然后我有一个模型名称 Carusage。 Carusage 的相关部分是这样的:

class Carusage(models.Model):
    person = models.ForeignKey(Person)
    start = models.DateTimeField()
    end = models.DateTimeField(null=True, blank=True)

Person 可以坐车,然后系统会创建一个新的 Carusage 实例并将其保存为 start 作为当前时间。然后当 Person 还车时,保存当前时间结束。

现在,在我的代码中,我有一个 Person 模型列表,我想在 Carusage 中检索每个 Person 的最新日期。因此,如果一个人刚刚还车,我想要与那个人相关联的最新 Carusage 的结束字段,如果这个人还有车,我想要开始字段。

最好我想在一个 SQL 语句中执行此操作,因为我的个人列表可能会变得非常大(下限~10,上限~10.000)。我试过这样的事情:

Carusage.objects.filter(person__in(person_list)).exclude(start__gte(time_now))

然后正在考虑注释,但想不出我将如何进行。

所以目前我正在这样做:

time_now = datetime.datetime.now()
time_list = []
for p in person_list:
    latest = Carusage.objects.filter(person=p).exclude(start__gte=time_now).only('start', 'end').latest('start')
    try:
        if latest.end<time_now:
            time=latest.end
        else:
            raise
    except:
        time=latest.start
    time_list.append(time)

显然我的代码运行速度很慢(500 人列表大约需要 5 秒)。运行这个/这些查询的“django 方式”是什么? 我想实现两件事:只为 Carusage 打一次数据库(至少不是 len(person_list) 次)并且只从数据库中获取相关时间(只需要最新的时间......)。 有什么方法可以实现吗?

【问题讨论】:

    标签: python django django-models django-queryset


    【解决方案1】:

    您实际上有两个单独的结果,您将它们与一个联合放在一起。

    1. 汽车退回。既是开始时间,也是结束时间。每人(可能)有很多车,而您只想要其中一辆。即使在纯 SQL 中,这也是一个相当复杂的查询,需要 HAVING 子句并导致(可能)性能下降。

    2. 尚未归还汽车。

    通常,当您有两个单独的规则时,您会更喜欢两个单独的查询。

    您实际上是在聚合按 Person 分组的 CarUsage,其开始(或结束)等于组的最大值。

    returned = Person.objects.filter( carusage__end__isnull=True ).annotate(Max('carusage__start'))
    not_returned = Person.objects.filter( carusage__end__isnull=False ).annotate(Max('carusage__end'))
    

    我想这就是你要找的。​​p>

    【讨论】:

    • 我可以看到那里的逻辑,我认为这适用于我的 sqlite-test 数据库。但是,当我尝试在使用带有 pyodbc 的 SQLServer 的生产环境中使用它时,我收到此错误:“无法比较或排序 text、ntext 和 image 数据类型,除非使用 IS NULL 或 LIKE 运算符。”更改 db-schema 不是一种选择,所以我想我会在这个上使用旧的原始 sql 代码...... :)
    • @Sindri Guðmundsson:“我想我会在这个上使用遗留的原始 sql 代码”。糟糕,糟糕的政策。您的数据模型包含的内容超出了您在问题中所揭示的内容。这意味着您可能必须使用aggregate 而不是annotate 来生成一些选定的字段——以避免图像、文本和ntext 字段。
    • Aggregate 只会给我一个日期。从这两个语句中,将注释替换为聚合我只会得到两个日期(一个是最近还车的日期,另一个是最近拿走但尚未归还的汽车)。为了澄清我需要每个人一个日期时间对象。还是您的回答中有什么我不理解的地方?
    • @Sindri Guðmundsson:“Aggregate 只会给我一个约会。” “我需要每个人一个日期时间对象。”我完全不明白你的评论。 aggregate 将结果集从 Person 对象更改为聚合值的简单元组。如果您需要更多数据,请将其列在aggregate 函数中。请阅读 Django 文档。请进行必要的更改。我不能为你写你的申请。
    【解决方案2】:

    通过这种方式使用异常,您可以保证获得慢代码。异常非常昂贵并且potentially slow down your code a lot(异常慢5-10倍)。无需引发和捕获异常,只需使用 else:

    if latest.end<time_now:
       time=latest.end
    else:
       time=latest.start
    

    你也可以简单地使用 Django 的 order_by 过滤器吗?例如,要按起始值获取给定人员订单的所有“Carusages”,请调用:

    Carusage.objects.filter(person=p).order_by('-start').all()
    

    【讨论】:

    • 我真的没有想过这个 try-except 子句。删除它,代码运行得更快,但只有一点点。您提出的查询仍然需要访问数据库 N 次。尽管您的回答稍微加快了我的代码速度,但它仍然无助于我尝试尽可能少地访问数据库。为速度升级 +1。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-06
    • 2019-10-19
    • 1970-01-01
    • 1970-01-01
    • 2019-07-20
    • 2013-11-28
    • 1970-01-01
    相关资源
    最近更新 更多