Django - 使用预取对象的多个注释

时间:2016-08-11 00:55:44

标签: sql django orm annotations

我的模型test包含两个 m2m 字段:foobar

我正在尝试注释这些相关字段的条件计数,即计算满足特定条件的相关对象。在查询集之外检索此信息不是一个选项,因为我需要使用带注释的字段来对结果进行排序。

我尝试了以下内容:

1。使用预取对象

from django.db.models import Prefetch, Count

prefetch_foo = Prefetch('foo_set', queryset=foo.objects.filter(<some condition>))
prefetch_bar = Prefetch('bar_set', queryset=bar.objects.filter(<some condition>))
result = test.objects.prefetch_related(prefetch_foo, prefetch_bar).annotate(n_foo=Count('foo'), n_bar=Count('bar'))

这不起作用,因为prefetch_related后会annotate被应用。

2。使用条件表达式

from django.db.models import Sum, Case, When
from django.db.models.fields import IntegerField

foo_sum = Sum(Case(When(foo__<some condition>, then=1), default=0,
                        output_field=IntegerField())))
bar_sum = Sum(Case(When(bar__<some condition>, then=1), default=0,
                        output_field=IntegerField())))
result = test.objects.annotate(n_foo=foo_sum, n_bar=bar_sum)

由于多个Sum注释上的此错误,这不起作用:https://code.djangoproject.com/ticket/10060

第3。使用RawSQL

sql = "SELECT SUM(CASE WHEN foo.<condition> "
      "THEN 1 ELSE 0 END) FROM app_test "
      "LEFT OUTER JOIN app_foo "
      "ON (app_test.id = foo.test_id) "
      "GROUP BY test.id"
result = test.objects.annotate(n_foo=RawSQL(sql, []))
# Same method for bar

我被困在这里,因为这会检索所有行的SUM,而我无法找到添加"WHERE test.id = <ID of the object the annotation corresponds to>"之类的内容的方法。

有没有办法从自定义SQL中获取正确的单行?还是另一种解决方法?

3 个答案:

答案 0 :(得分:1)

Django中的Count函数现在具有一个filter参数,它应该是您要寻找的参数。

See documentation

在这种情况下:

result = test.objects.annotate(n_foo=Count('foo_set', filter=Q(<some condition>)), n_bar=Count('bar_set', filter=Q(<some condition)))

应该给出预期的结果。

答案 1 :(得分:0)

我不确定你是否可以用Django ORM做到这一点(因为那个8岁的bug)但是在RawSQL上使用HAVING应该可行。你尝试过这样的事吗?

sql = """SELECT SUM(CASE WHEN app_foo.<condition> 
      THEN 1 ELSE 0 END) FROM app_test 
      LEFT OUTER JOIN app_foo 
      ON (app_test.id = app_foo.test_id) 
      GROUP BY app_test.id 
      HAVING app_test.id = <ID of the object the annotation corresponds to>"""

Ť

答案 2 :(得分:0)

我已对您的SQL进行了更改。您不使用FROM来获取app_test。你以前见过SQL子查询吗?

sql = """
  SELECT SUM(CASE WHEN app_foo.<condition> THEN 1 ELSE 0 END) 
  FROM app_foo 
  WHERE app_test.id = app_foo.id  # app_test comes from outside the sub query.

  """
result = test.objects.annotate(n_foo=RawSQL(sql, []))

运行的实际查询类似于......

SELECT app_test.*, (
  SELECT SUM(CASE WHEN app_foo.<condition> THEN 1 ELSE 0 END) 
  FROM app_foo 
  WHERE app_test.id = app_foo.id  ) AS n_foo
FROM app_test