9

假设我想显示一个按他们最近的冲刺时间排序的跑步者列表。

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)

这是我将在 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

Django QuerySet 文档指出:

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

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

请注意,因为在此示例中并不明显:Runner 表将有数百个条目,sprint 也将有数百个,并且在以后的某些日子里可能有数千个条目。数据将显示在分页视图中,因此不能在 Python 中进行排序。

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

4

2 回答 2

4

我认为没有办法通过 ORM 仅通过一个查询来执行此操作,您可以获取跑步者列表并使用注释添加他们最新的 sprint id - 然后过滤并订购这些 sprint。

>>> 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

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

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

于 2013-03-02T02:13:45.367 回答
0
def view_name(request):
    spr = Sprint.objects.values('runner', flat=True).order_by(-created).distinct()
    runners = []
    for s in spr:
        latest_sprint = Sprint.objects.filter(runner=s.runner).order_by(-created)[:1]
        for latest in latest_sprint:
            runners.append({'runner': s.runner, 'time': latest.time})

    return render(request, 'page.html', {
            'runners': runners,
    })


{% for runner in runners %}
    {{runner.runner}} - {{runner.time}}
{% endfor %}
于 2013-03-01T10:12:45.943 回答