从查询集中构建最高价格列表的最有效方法?
Most efficient way to build list of highest prices from 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 调试工具栏看到这段代码产生了太多 mysql 查询。
有人可以建议一种更好的方法来构建此列表,该列表可能会命中 MySQL 一两次吗?
我发誓这就是我能够处理它的方式,但看来我一定是弄错了。
我认为 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 解决方案,它将在幕后发生。
虽然您处理的是很常见的情况,但似乎缺乏明显的解决方案。
解决方案 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])
这应该绝对有效,并在两个查询中获取所有内容,但是非常浪费,因为它会将与给定的 Companies
集相关的所有 Cars
加载到内存中。请注意 list()
部分不是可选的:无论您使用切片或索引,都会复制查询集并生成单独的数据库查询,因此否定预取,而实例化列表将使用所述预取的结果。
如果您之后需要访问公司,例如 Car.company
,请不要回避使用 select_related
,正如 Erik 在评论中所建议的那样。
向公司添加一个名为 "priciest_car" 的字段并覆盖保存,以便每次保存公司时,循环遍历它的相关汽车并将最昂贵的设置为 priciest_car。然后当你需要为每个公司调用最昂贵的汽车时,你可以循环遍历每个公司并将 company.priciest_car
添加到列表中。这是一个循环,一个 sql 调用每个迭代。唯一的额外工作是当您保存一家公司时,但这将是每个公司,因此不会花费太长时间。如果是这样,请找到一种方法,使其仅在您知道它已被更改时才设置 "priciest_car" 字段。
在我的应用程序的一页中,我试图显示每家公司最昂贵的汽车。我的模型大致如下所示:
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 调试工具栏看到这段代码产生了太多 mysql 查询。
有人可以建议一种更好的方法来构建此列表,该列表可能会命中 MySQL 一两次吗?
我发誓这就是我能够处理它的方式,但看来我一定是弄错了。
我认为 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 解决方案,它将在幕后发生。
虽然您处理的是很常见的情况,但似乎缺乏明显的解决方案。
解决方案 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])
这应该绝对有效,并在两个查询中获取所有内容,但是非常浪费,因为它会将与给定的 Companies
集相关的所有 Cars
加载到内存中。请注意 list()
部分不是可选的:无论您使用切片或索引,都会复制查询集并生成单独的数据库查询,因此否定预取,而实例化列表将使用所述预取的结果。
如果您之后需要访问公司,例如 Car.company
,请不要回避使用 select_related
,正如 Erik 在评论中所建议的那样。
向公司添加一个名为 "priciest_car" 的字段并覆盖保存,以便每次保存公司时,循环遍历它的相关汽车并将最昂贵的设置为 priciest_car。然后当你需要为每个公司调用最昂贵的汽车时,你可以循环遍历每个公司并将 company.priciest_car
添加到列表中。这是一个循环,一个 sql 调用每个迭代。唯一的额外工作是当您保存一家公司时,但这将是每个公司,因此不会花费太长时间。如果是这样,请找到一种方法,使其仅在您知道它已被更改时才设置 "priciest_car" 字段。