同时过滤相关对象的计数和满足Django条件的相关对象的计数

时间:2013-01-28 11:36:01

标签: sql django orm

所以我有相当于这个的模型(显然非常简化):

class Mystery(models.Model):
    name = models.CharField(max_length=100)

class Character(models.Model):
    mystery = models.ForeignKey(Mystery, related_name="characters")
    required = models.BooleanField(default=True)

基本上,在每一个谜团中都有许多角色,这些角色对于故事是否至关重要。能够揭开神秘面纱的最小演员数量是这个神秘所需的角色数量;最大数字是神秘的总字符数。

现在我正在尝试查询某些特定演员可以演奏的谜团。使用Django的过滤和注释功能的方式似乎很简单;毕竟,这两个查询都可以正常工作:

# Returns mystery objects with at least x characters in all
Mystery.objects.annotate(max_actors=Count('characters', distinct=True)).filter(max_actors__gte=x)

# Returns mystery objects with no more than x required characters
Mystery.objects.filter(characters__required=True).annotate(min_actors=Count('characters', distinct=True)).filter(min_actors__lte=x)

但是,当我尝试将两者合并时......

Mystery.objects.annotate(max_actors=Count('characters', distinct=True)).filter(characters__required=True).annotate(min_actors=Count('characters', distinct=True)).filter(min_actors__lte=x, max_actors__gte=x)

......它不起作用。 min_actors和max_actors都包含最大数量的actor。正在运行的实际查询的相关部分如下所示:

SELECT `mysteries_mystery`.`id`,
    `mysteries_mystery`.`name`,
    COUNT(DISTINCT `mysteries_character`.`id`) AS `max_actors`,
    COUNT(DISTINCT `mysteries_character`.`id`) AS `min_actors`
FROM `mysteries_mystery`
    LEFT OUTER JOIN `mysteries_character` ON (`mysteries_mystery`.`id` = `mysteries_character`.`mystery_id`)
    INNER JOIN `mysteries_character` T5 ON (`mysteries_mystery`.`id` = T5.`mystery_id`)
WHERE T5.`required` = True
GROUP BY `mysteries_mystery`.`id`, `mysteries_mystery`.`name`

...这清楚地表明,当Django在字符表上创建第二个连接时(表的第二个副本被别名为T5),该表实际上并未在任何地方使用,而且两个从非别名版本中选择计数,这显然会产生相同的结果。

即使我尝试使用extra子句从T5中进行选择,我也会被告知没有T5这样的表,即使检查输出查询显示它仍然将第二个字符表别名为T5。使用extra子句执行此操作的另一种尝试是这样的:

Mystery.objects.annotate(max_actors=Count('characters', distinct=True)).extra(select={'min_actors': "SELECT COUNT(*) FROM mysteries_character WHERE required = True AND mystery_id = mysteries_mystery.id"}).extra(where=["`min_actors` <= %s", "`max_actors` >= %s"], params=[x, x])

但这不起作用,因为我不能在WHERE子句中使用计算字段,至少在MySQL上。如果我只能使用HAVING,但是唉,Django的.extra()does not and will never允许你设置HAVING参数。

有没有办法让Django的ORM做我想做的事?

2 个答案:

答案 0 :(得分:0)

如何组合你的Count()s:

Mystery.objects.annotate(max_actors=Count('characters', distinct=True),min_actors=Count('characters', distinct=True)).filter(characters__required=True).filter(min_actors__lte=x, max_actors__gte=x)

这似乎对我有用,但我没有用您的确切型号进行测试。

答案 1 :(得分:0)

已经有几个星期没有建议的解决方案了,所以这就是我最终要做的事情,对于其他可能正在寻找答案的人来说:

Mystery.objects.annotate(max_actors=Count('characters', distinct=True)).filter(max_actors__gte=x, id__in=Mystery.objects.filter(characters__required=True).annotate(min_actors=Count('characters', distinct=True)).filter(min_actors__lte=x).values('id'))

换句话说,过滤第一个计数和与匹配第二个计数的显式子查询中的ID匹配的ID。有点笨重,但它适合我的目的。