2016-11-28 52 views
4

在我的应用程序的一个页面中,我试图为每家公司展示最昂贵的汽车。我的模型看起来大致是这样的:从queryset中建立最高价格列表的最有效方法?

class Company(models.Model): 
    id = models.IntegerField(primary_key=True) 
    company = models.CharField(max_length=100) 
    headcount = models.IntegerField(null=False) 
    info = models.CharField(max_length=100) 

class Car(models.Model): 
    id = models.IntegerField(primary_key=True) 
    company_unique = models.ForeignKey(Company) 
    company = models.CharField(max_length=50) 
    name = models.CharField(max_length=100) 
    price = models.DecimalField(max_digits=9, decimal_places=2, default=0.00) 

所以,我要建立一个由每家公司的最昂贵的汽车对象的列表。

我走近这样的问题:

company_list = Company.objects.all() 
most_expensive = [] 
for company in company_list: 
    most_expensive.append(Car.objects.filter(company_unique=company.id).order_by("-price")[0]) 

然而,这似乎是一个非常低效的方法。我可以用Django Debug Toolbar看到,这段代码让太多的mysql查询变得很麻烦。

有人可以建议一个更好的方式来建立这个名单,这将打击MySQL可能只是一次或两次?

+0

使用这些内置函数可能会减少查询数量:https://docs.djangoproject.com/en/1.10/topics/db/optimization/#retrieve-everything-at-once-if-you-know-you-will -need-it – Erik

回答

1

虽然你正在处理的是相当普遍的情况,但显然缺乏明显的解决方案。

解决方案1 ​​,发现于this article。你也许可以尝试的东西沿着这些路线:

companies = Company.objects.annotate(max_price=Max('car__price')) 
values = tuple((company.id, company.max_price) for company in companies) 

expensive_cars = Car.objects.extra(where=['(company_unique_id, price) IN %s' % (values,)]) 

不能说我喜欢的解决方案 - .extra应避免 - 但我想不出更好的办法。我也不完全确定这会起作用。

解决方案2,次优。你可以使用custom Prefetch object

prefetch = Prefetch('cars', queryset=Car.objects.order_by('-price'), to_attr='cars_by_price') 
companies = Company.objects.prefetch_related(prefetch) 

most_expensive_cars = [] 
for company in companies: 
    most_expensive_cars.append(list(company.cars_by_price.all())[0]) 

这应该肯定的工作,并在两个查询取一切,反而是极其浪费,因为它会加载所有Cars与给定的Companies到内存中。请注意,list()部分不是可选的:无论您采取切片还是索引,都会复制查询集并生成单独的数据库查询,因此会取消预取,而实例化列表将使用所述预取的结果。

如果您之后需要访问公司,如Car.company,请不要回避使用select_related,正如Erik在评论中所建议的那样。

+0

我认为解决方案1是我要走的路。在我的情况下,我发现我必须将这两个值转换为字符串,例如:对于公司中的公司,'values = tuple((str(company.id),str(company.max_price))''否则我会得到MySQL错误。 – Kirkman14

0

我发誓这是我能够处理它,但似乎我一定是错了。

我认为这是可能的Aggregation

most_expensive = Car.objects.values('company_unique').annotate(Max('price')) 

下面是原始SQL,它有它的好处,但我觉得有可能是一个更清洁的方式:

from django.db import connection 

cursor = connection.cursor() 
cursor.execute("SELECT Max(price), company_unique FROM Car GROUP BY company_unique"); 
price_company = cursor.fetchall() 

# This still does one query per car, only it fetches one item at a time. 
most_expensive = [Cars.objects.get(price=pc[0],company_unique=pc[1]) 
        for pc in price_company] 

如果您真的想限制为一个查询,th恩,你可能能够利用raw

most_expensive = Cars.objects.raw(""" 
    SELECT * FROM Cars 
    INNER JOIN 
     (SELECT Max(price) as price, company_unique FROM Car GROUP BY company_unique) m 
     ON m.price = Cars.price, m.company_unique = Cars.company_unique 
""") 

问题使用raw的是,它不是数据库无关,因此任何重构将需要重新编写此查询涉及。 (例如,Oracle具有不同的辅助查询语法)。

我觉得我应该指出,无论如何,将执行SELECT Max(price) as price, company_unique FROM Car GROUP BY company_unique查询 - 如果您使用的是更多的Django本机解决方案,它将在幕后发生。

+0

该查询集似乎不再由Car对象组成。至少,它不再拥有像'name'这样的Car对象的所有字段。当我将它传递到我的模板中时,将使用正确的行数构建表,但每个单元都是空的。 – Kirkman14

+0

这是一个不同的问题。你的模板中可能有一个错误(这不包括在问题中,所以它不是相关的)这真的回答了这个问题 – e4c5

+0

我不明白这是如何回答这个问题的。如果你阅读OP给出的例子,很明显他期望得到Car实例,而不仅仅是最高价格。 –

0

向公司添加一个名为“priciest_car”的字段并覆盖保存,以便每次保存公司时,您都会循环访问相关的汽车并将最贵的一个设置为priciest_car。那么当您需要为每家公司调用最昂贵的汽车时,您可以循环访问每家公司,并将company.priciest_car添加到列表中。这是一个循环,一个sql调用每个循环。唯一的额外工作是在您拯救一家公司的时候,但每个公司都是这样,因此不应该花太长时间。如果是这样,找到一种方法,只有当你知道它已被改变时,才设置“priciest_car”字段。

相关问题