23

我正在 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 对象在该用户的 Consumption 表中出现的次数(用户消耗食物的次数)排序。

我正在尝试以下内容:

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

但这当然会计算与 Food 对象相关的所有 Consumption 对象,而不仅仅是与用户关联的那些。我是否需要更改我的模型,或者我只是在查询中遗漏了一些明显的东西?

这是一个非常耗时的操作(除其他外,它用于填充前端中的自动完成字段)并且 Food 表有几千个条目,所以我宁愿在数据库端进行排序,而不是执行蛮力方法并迭代结果:

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

然后使用 python sort 对它们进行排序。我不认为这种方法会随着用户群的增加而很好地扩展,我想从一开始就将数据库设计为未来的证明。

有任何想法吗?

4

2 回答 2

38

也许是这样的?

Food.objects.filter(consumer__user=user)\
            .annotate(consumption_times=Count('consumer'))\
            .order_by('consumption_times')
于 2009-09-08T21:00:38.527 回答
22

我有一个非常相似的问题。基本上,我知道您想要的 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')
于 2009-11-25T20:17:40.433 回答