2013-03-01 50 views
7

让我们假设我想显示按最新Sprint时间排序的跑步者列表。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) 

这是什么,我会在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 documentation状态:

这是允许指定一个多值字段订购结果 由(例如,ManyToManyField字段)。通常这不会是一个明智的事情,它确实是一种高级使用功能。 但是,如果您知道您的查询集的筛选或可用数据 意味着您所选择的主要项目中每个 只有一个排序数据块,那么排序可能恰好是您想要执行的操作的 。谨慎使用多值字段排序和 确保结果符合您的期望。

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

一个说明,因为它不是在这个例子明显:亚军表将有几个一百个条目,冲刺也将有几百个,并在稍后几天可能有几千条。数据将显示在分页视图中,因此Python中的排序不是一种选择。

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

回答

2

我不认为有一种方法,通过ORM只有一个查询要做到这一点,你可以抢跑者的列表,并使用annotate增加了他们的最新冲刺的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 

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

几千条记录并不是那么多,对于这些类型的数字,这应该工作得很好。如果你开始遇到问题,我建议你咬紧牙关,使用原始的SQL。

+0

这不会造成相对较高的内存使用量吗?据我所知,它至少将每个跑步者都拉进记忆中,并建立一个相当大的冲刺ID列表。在数据库中有几百名跑步者的每个页面视图中进行此操作,都会让我感觉不舒服。我猜,这是缓存开始的地方。 – Strayer 2013-03-04 12:29:19

+1

经过10000人的测试后,它使用的RAM少于10MB(实际上是3MB)。如果你认为你需要的不仅仅是这些,你应该使用原始SQL。与往常一样,最好的方法是首先进行分析 - 而不是推测。不成熟的优化和所有...... – Matt 2013-03-04 16:28:00

+0

而且,几百条记录确实不是很多...肯定不足以担保性能优化。几十万条记录通常是你开始思考它的地方,即使这样,通常也不是什么问题(折腾成一两个索引并解决)。 – Matt 2013-03-04 16:42:55

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 %} 
+0

该问题没有得到最新的sprint,而是通过其最新sprint“time”字段排序了Runner QuerySet。 – Strayer 2013-03-01 10:15:40

+0

这确实有用,是的。问题是这会将运行程序的顺序移动到应用程序中,这会导致至少大量的内存使用量和相对较高的CPU使用率。请参阅有关表格尺寸的更新问题。这种方法的另一个问题是它不会显示任何没有冲刺的跑步者。虽然这也可以在python代码中解决,但这对于数据库来说是一个很好的工作,因为它可以利用它的索引和缓存。这适用于小型数据库,但我们的SysAdmin会杀了我,如果我这样做;) – Strayer 2013-03-01 11:03:31

+0

嗯......这很难。我们也是一样,因为我的雇主期望,我在编码方面小心谨慎。 :) – catherine 2013-03-01 11:25:39

相关问题