3

I am building a complicated queryset over a complicated datamodel. The queryset must return a selection of model objects A with an annotation ann.

For computing ann, I need an auxiliary annotation aux, but no aux is allowed to exist in the final results of the queryset.

qs = A.objects.filter(...)  # complicated
qs = qs.annotate(aux=...)  # complicated
qs = qs.annotate(ann=Case(
    When(condition=Q(aux=0), 
         then Value('some')),
    When(condition=Q(aux_gt=0),
         then Value('other'))))

How to return qs with ann but without aux?

(Alternatively: If aux is a count and ann discriminates zero and non-zero aux, is there a better way to approach the whole problem?)

Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88

2 Answers2

7

Django 3.2 introduced a feature to solve specifically this:

https://docs.djangoproject.com/en/3.2/ref/models/querysets/#alias

alias() works like annotate() except only for other queryset methods such as annotate(): it doesn't stay in the final output.

qs = A.objects.filter(...)
qs = qs.alias(aux=...)  # using alias here instead of annotate
qs = qs.annotate(ann=Case(
When(condition=Q(aux=0), 
     then Value('some')),
When(condition=Q(aux_gt=0),
     then Value('other'))))
Cameron Lee
  • 973
  • 9
  • 11
2

Ugly hack:

A simple, if highly unelegant method is to rename aux to be called ann as well. That way, the second annotation overwrites the first and the first will no longer exist in the output.

qs = A.objects.filter(...)  # complicated
qs = qs.annotate(ann=...)  # complicated
qs = qs.annotate(ann=Case(
    When(condition=Q(ann=0), 
         then Value('some')),
    When(condition=Q(ann_gt=0),
         then Value('other'))))

This appears to work. Tested with Django 1.11 on SQlite and PostgreSQL.

But there must be a better way, pleeeease.

Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88