我已经阅读了this和this,但它们并没有解决我的问题,因为它们通过与硬编码的数字进行比较来进行最终的“计数”。我想比较一个数字,它是配方本身所有成分的总和。
让我们想象一下,我的冰箱中有一些成分,带有id(= id的数组)。我想看看我能做些什么。我的模特是这样的:
class Ingredient(models.Model):
label = models.CharField(max_length=200, null=True, blank=True,
default=None)
description = models.TextField(null=True, blank=True, default=None)
class Unit(models.Model):
label = models.CharField(max_length=200, null=True, blank=True,
default=None)
abbr = models.CharField(max_length=20, null=True, blank=True,
default=None)
class IngredientUnit(models.Model):
ingredient = models.ForeignKey(Ingredient, null=False, blank=True)
unit = models.ForeignKey(Unit, null=False, blank=True)
measurable = models.BooleanField(default=True, null=False, blank=True)
is_int = models.BooleanField(default=True, null=False, blank=True)
value = models.FloatField(null=True, blank=True, default=0.0)
class Recipe(models.Model):
label = models.CharField(max_length=200, null=True, blank=True,
default=None)
description = models.TextField(null=True, blank=True, default=None)
ingredients = models.ManyToManyField(IngredientUnit)
我想这样做:'选择所有具有 all 的配方'成分pk的成分。例如:“经典香草蛋糕”有这些成分:鸡蛋,通用面粉,发酵粉,小苏打,黄油,糖,香草,酪乳。如果缺少一个,“经典香草蛋糕”不应该出现在结果查询中。相反,如果有更多成分而不是那些,那么“经典香草蛋糕”应该始终存在于结果查询中。
到目前为止,我已经这样做了,但它没有用。
ingredient_ids = self.request.POST.getlist('ingredient[]', [])
if len(ingredient_ids):
recipes = Recipe.objects\
.filter(ingredients__in=ingredient_ids)\
.annotate(nb_ingredients=Count('ingredients'))\
.filter(nb_ingredients=len(ingredient_ids))
print([a for a in recipes])
问题是nb_ingredients=len(ingredient_ids)
应为nb_ingredients=the number of the ingredients of the current recipe
我该怎么做?
答案 0 :(得分:0)
我认为您可以尝试排除缺少成分的食谱。
Recipe.objects.exclude(~Q(ingredients__in=ingredient_ids)).distinct()
测试它,它不起作用。
理论上,你想要的是什么:
问题是nb_ingredients = len(ingredient_ids)应该是 nb_ingredients =当前食谱的成分数
应该在过滤器之前使用一个注释,在过滤器之后使用一个注释(因为the order of annotation and filter matters)。
即。像这样的东西:
recipes = (Recipe.objects
.annotate(num_ingredients=Count('ingredients'))
.filter(ingredients__in=ingredient_ids)
.annotate(found_ingredients=Count('ingredients'))
.filter(num_ingredients=F('found_ingredients'))
)
然而,当我测试它时,它没有工作,因为ORM没有从过滤器重用正确的JOIN。这可能是某种错误,我只是在邮件列表中打开question。
如果您使用的是Django 1.11,另一种方法是使用新的Subquery-expressions,您可以试试吗。
recipes = (Recipe.objects
.filter(ingredients__in=ingredient_ids)
.annotate(found_ingredients=Count('ingredients', distinct=True))
.annotate(num_ingredients=Count(Subquery(
Ingredient.objects.filter(recipe=OuterRef('pk')).only('pk')),
distinct=True)
)
.filter(num_ingredients=F('found_ingredients'))
)
答案 1 :(得分:0)
我找到了!不能避免双重查询,但它就像一个魅力。这是解决方案:
group by
)过滤食谱中的成分,计算找到的成分总数 感觉就像使用大锤来破解坚果(即使第一个查询过滤并消除了很多食谱),但是如果你有一个更好的解决方案,那就是你的男人!
recipes = Recipe.objects \
.annotate(found=Count('*'))\
.filter(ingredients__in=ingredient_ids)
for recipe in recipes:
a = Recipe.objects.annotate(total=Count('ingredients')).filter(
pk=recipe.pk, total=recipe.found)
print("Recipe found:", str(a))
例如,如果成分的ID为[1, 2, 3, 4, 5]
,您将获得这两个查询:
SELECT "app_recipe"."id", "app_recipe"."label", "app_recipe"."description",
COUNT(*) AS "found" FROM "app_recipe"
INNER JOIN "app_recipe_ingredients"
ON ("app_recipe"."id" = "app_recipe_ingredients"."recipe_id")
WHERE "app_recipe_ingredients"."ingredientunit_id" IN (1, 2, 3, 4, 5)
GROUP BY "app_recipe"."id", "app_recipe"."label", "app_recipe"."description";
第二个循环将基于如下所示的配方进行查询:
SELECT "app_recipe"."id", "app_recipe"."label", "app_recipe"."description",
COUNT("app_recipe_ingredients"."ingredientunit_id") AS "total"
FROM "app_recipe"
LEFT OUTER JOIN "app_recipe_ingredients"
ON ("app_recipe"."id" = "app_recipe_ingredients"."recipe_id")
WHERE "app_recipe"."id" = 1
GROUP BY "app_recipe"."id", "app_recipe"."label", "app_recipe"."description"
HAVING COUNT("app_recipe_ingredients"."ingredientunit_id") = 5;