Django:根据最新的子模型字段对QuerySet进行排序

Str*_*yer 7 django django-models django-queryset

让我们假设我想显示按其最新冲刺时间排序的跑步者列表.

class Runner(models.Model):
    name = models.CharField(max_length=255)

class Sprint(models.Model):
    runner = models.ForeignKey(Runner)
    time = models.PositiveIntegerField()
    created = models.DateTimeField(auto_now_add=True)
Run Code Online (Sandbox Code Playgroud)

这是我在SQL中做的快速草图:

SELECT runner.id, runner.name, sprint.time
FROM runner
LEFT JOIN sprint ON (sprint.runner_id = runner.id)
WHERE 
  sprint.id = (
    SELECT sprint_inner.id
    FROM sprint as sprint_inner
    WHERE sprint_inner.runner_id = runner.id
    ORDER BY sprint_inner.created DESC
    LIMIT 1
  )
  OR sprint.id = NULL
ORDER BY sprint.time ASC
Run Code Online (Sandbox Code Playgroud)

Django的查询集文档指出:

允许指定多值字段以按结果排序(例如,ManyToManyField字段).通常这不是一件明智的事情,它确实是一种高级的使用功能.但是,如果您知道您的查询集的过滤或可用数据意味着您选择的每个主要项目只有一个订购数据,那么排序可能正是您想要做的.谨慎使用多值字段的排序,并确保结果符合您的预期.

我想我需要在这里应用一些过滤器,但我不确定Django究竟是什么期望......

一个注意事项,因为在这个例子中并不明显:Runner表将有几百个条目,sprint也将有几百个,在一些晚些时候可能有几千个条目.数据将以分页视图显示,因此无法在Python中进行排序.

我看到的唯一另一种可能性是自己编写SQL,但我想不惜一切代价避免这种情况.

Mat*_*att 4

我不认为有一种方法可以通过仅使用一个查询的 ORM 来做到这一点,您可以获取跑步者列表并使用注释添加他们最新的冲刺 ID,然后过滤并排序这些冲刺。

>>> from django.db.models import Max

# all runners now have a `last_race` attribute,
# which is the `id` of the last sprint they ran
>>> runners = Runner.objects.annotate(last_race=Max("sprint__id"))

# a list of each runner's last sprint ordered by the the sprint's time,
# we use `select_related` to limit lookup queries later on
>>> results = Sprint.objects.filter(id__in=[runner.last_race for runner in runners])
...                         .order_by("time")
...                         .select_related("runner")

# grab the first result
>>> first_result = results[0]

# you can access the runner's details via `.runner`, e.g. `first_result.runner.name`
>>> isinstance(first_result.runner, Runner)
True

# this should only ever execute 2 queries, no matter what you do with the results
>>> from django.db import connection
>>> len(connection.queries)
2
Run Code Online (Sandbox Code Playgroud)

这非常快,并且仍然会利用数据库的索引和缓存。

几千条记录并不算多,这对于这些类型的数字应该非常有效。如果您开始遇到问题,我建议您硬着头皮使用原始 SQL。