从查询集构建最高价格列表的最有效方法是什么?

时间:2016-11-28 17:17:38

标签: python mysql django django-queryset django-orm

在我的应用的一页中,我试图为每家公司展示最昂贵的汽车。我的模型看起来大致如下:

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)

所以,我想建立一个包含每个公司最昂贵的Car对象的列表。

我解决了这个问题:

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查询。

有人可以建议一个更好的方法来构建这个列表,这个列表可能只会打一两次吗?

3 个答案:

答案 0 :(得分: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()部分不是可选的:无论您采用切片还是索引,都会复制查询集并生成单独的数据库查询,从而否定预取,而实例化列表将使用所述预取的结果。 / p>

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

答案 1 :(得分: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]

如果您真的想将其限制为一个查询,那么您可以利用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本地解决方案,它将在幕后发生。

答案 2 :(得分:0)

向公司添加一个名为" priciest_car"并覆盖保存,以便每次保存公司时,您都会遍历它的相关汽车,并将最昂贵的汽车设置为priciest_car。然后,当您需要为每家公司拨打最昂贵的汽车时,您可以循环浏览每家公司并将company.priciest_car添加到列表中。它是一个循环,每次尝试一次sql调用。唯一的额外工作是当你拯救一家公司,但它将是每家公司所以它不应该花费太长时间。如果确实如此,那么找到一种方法只设置" priciest_car"字段只有当你知道它被改变了。