我正在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对象的排序次数,该对象按Food对象出现在该用户的消耗表中的次数(用户食用食物的次数)。
我正在尝试以下方面:
Food.objects.all().annotate(consumption_times = Count(consumer)).order_by('consumption_times')`
但是,这当然会计算与Food对象相关的所有Consumer对象,而不仅仅是与用户关联的对象。我是否需要更改模型,或者我只是遗漏了查询中显而易见的内容?
这是一个非常关键的时间操作(除此之外,它用于填充前端的自动完成字段)和Food表有几千个条目,所以我宁愿在数据库端进行排序,而不是做蛮力方法并迭代结果:
Consumption.objects.filter(food=food, user=user).count()
然后使用python排序对它们进行排序。我不认为随着用户群的增加,这种方法会扩展得很好,我想从一开始就设计数据库作为未来的证据。
有什么想法吗?
答案 0 :(得分:24)
也许是这样的?
Food.objects.filter(consumer__user=user)\
.annotate(consumption_times=Count('consumer'))\
.order_by('consumption_times')
答案 1 :(得分: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')
此外,只是能够更轻松地添加自己的复杂聚合函数会很不错,但与此同时,这是一个增加聚合函数的hack。
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')