2016-11-28 47 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」字段。

相關問題