Django 1.8 conditional annotation results in INNER

2019-07-18 05:08发布

The models:

class Bar(GenericModel):
    ...

class Foo(GenericModel):
    bar = models.ForeignKey(Bar, related_name='foo_bar')

The query:

bars = Bar.objects
       .prefetch_related('foo_bar')
       .annotate(sum_foo=Sum(
                             Case(
                                  When(foo_bar__is_deleted=False, then='foo_bar__amount'),
                                  default=Value(0),
                                  output_field=IntegerField()
                                 )
                            )
                )

The former results in an inner join: SELECT ... FROM "bar" INNER JOIN "foo" ON ( "bar"."id" = "foo"."bar_id" ) ...

What I intend to obtain is a LEFT OUTER JOIN (a full list of "bar" objects annotated with "foo.amount" sums, or 0s if "foo" related to "bar" doesn't exist) instead of the INNER JOIN? Is it possible to do without falling back to raw SQL?

2条回答
聊天终结者
2楼-- · 2019-07-18 06:01

This way seems to work correctly:

bars = Bar.objects
       .prefetch_related('foo_bar')
       .annotate(sum_foo=Sum(
                             Case(
                                  When(Q(foo_bar__is_deleted=False) | Q(foo_bar__is_deleted=None),
                                                 then='foo_bar__amount'),
                                  default=Value(0),
                                  output_field=IntegerField()
                                 )
                            ),
                )
查看更多
闹够了就滚
3楼-- · 2019-07-18 06:02

This is a known bug, corrected in Django 1.8.3 (release notes).

As you noted, the issue is that an INNER JOIN is being created, filtering out Bar objects when there's no corresponding relation to Foo objects.

Using a Django version higher than 1.8.3 will solve the issue.

查看更多
登录 后发表回答