2009-09-08 55 views
14

我建立在Django食物日誌數據庫相關模型進行排序和我有一個查詢相關的問題。如何通過註解COUNT()在Django的

我已經設置了我的模型,包括(除其他事項外),通過消費模型通過M2M場「消費者」連接到用戶模型中的食品模型。食品模型描述小菜和消費模型描述食品(日期,金額等)的用戶的消費。

class Food(models.Model): 
    food_name = models.CharField(max_length=30) 
    consumer = models.ManyToManyField("User", through=Consumption) 

class Consumption(models.Model): 
    food = models.ForeignKey("Food") 
    user = models.ForeignKey("User") 

我希望創建一個返回該食品對象出現在消費表用戶(次用戶消費的食品數量)的次數下令所有食品對象的查詢。

我想東西線:

Food.objects.all().annotate(consumption_times = Count(consumer)).order_by('consumption_times')` 

但是這當然算有關食品的對象,不只是與用戶相關聯的那些所有的消費對象。我是否需要更改我的模型,或者我只是錯過了查詢中顯而易見的事情?

這是一個相當耗時的操作(其中包括用於填充前端的自動填充字段),而Food表有幾千個條目,所以我寧願在數據庫中進行排序結束,而不是使用暴力方法並迭代結果:

Consumption.objects.filter(food=food, user=user).count() 

然後使用python sort對它們進行排序。我不認爲這方法將很好地進行縮放隨着用戶基數增大,我想數據庫,我可以從一開始就設計爲面向未來。

任何想法?

+0

的可能的複製[由一個ForeignKey字段的計數盤?(http://stackoverflow.com/questions/2501149/order-by-count-of-a-foreignkey-field) –

回答

21

也許這樣的事情?

Food.objects.filter(consumer__user=user)\ 
      .annotate(consumption_times=Count('consumer'))\ 
      .order_by('consumption_times') 
+0

但這隻返回在某個時間消耗的食物對象,不是嗎?我想返回所有的食物對象,但是按照最經常消耗的順序。 如果我按用戶過濾,我不會得到尚未消耗的食物。一個想法可能是做兩個查詢,首先像你建議的那樣,讓所有的食物消耗至少一次,然後沿着Food.objects.exclude(consumer__user = user)行填寫列表,並用這些查詢填寫。這會起作用嗎? –

+0

是的,2個問題將是我會怎麼做。 – SmileyChris

19

我有一個非常類似的問題。基本上,我知道你想要的SQL查詢:

SELECT food.*, COUNT(IF(consumption.user_id=123,TRUE,NULL)) AS consumption_times 
     FROM food LEFT JOIN consumption ON (food.id=consumption.food_id) 
     ORDER BY consumption_times; 

我希望是,你可以混合聚合函數和F表達,詮釋˚F表情沒有聚合函數,有更豐富的業務/功能F表達式,並且具有基本上是自動F表達註釋的虛擬字段。所以,你可以這樣做:

Food.objects.annotate(consumption_times=Count(If(F('consumer')==user,True,None)))\ 
      .order_by('consumtion_times') 

此外,剛剛能夠能夠更容易地添加自己的複雜的聚合功能將是很好的,但在此期間,這裏是增加了集合函數來做到這一點黑客攻擊。

from django.db.models import aggregates,sql 
class CountIf(sql.aggregates.Count): 
    sql_template = '%(function)s(IF(%(field)s=%(equals)s,TRUE,NULL))' 
sql.aggregates.CountIf = CountIf 

consumption_times = aggregates.Count('consumer',equals=user.id) 
consumption_times.name = 'CountIf' 
rows = Food.objects.annotate(consumption_times=consumption_times)\ 
        .order_by('consumption_times') 
+0

這是真棒!謝謝你,你救了我的一天!我會盡量讓它看起來更漂亮一點,但是你絕對應該把它寫入django的trac。 –