#34551: Case-When aggregation over aggregated fields doesn't work since 4.2
-------------------------------------+-------------------------------------
Reporter: Denis Roldán | Owner: nobody
Type: Bug | Status: closed
Component: Database layer | Version: 4.2
(models, ORM) |
Severity: Normal | Resolution: needsinfo
Keywords: orm, aggregate, | Triage Stage:
case, when, field error, bug | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Denis Roldán:
Old description:
> This same QuerySet was working on 4.1.X and prior versions and stopped
> working on 4.2.X:
>
> {{{
> annotated_users = users_qs.annotate(
> total_things=Subquery(
> OtherModel.objects.filter(user_id=OuterRef("pk")).annotate(
> total_objs=F("total")
> ).values("total_objs")
> )
> )
>
> annotated_users.aggregate(
> sum_total_objs=Sum("total_things"),
> avg_conversion_rate=Case(
> When(
> sum_total_objs=0,
> then=0,
> ),
> default=Round(
> (Sum("sum_total_confirmed_objs") /
> Sum("sum_total_objs")) * 100, 2
> ),
> output_field=FloatField(),
> )
> )
> }}}
>
> As you can see `sum_total_objs` is an aggregated field that is also used
> on a second field to calculate the conversion rate. To avoid a zero
> division problem, we were using a Case-When clause over that field. It
> works well on any 4.1 and prior versions but stopped working since 4.2,
> raising a `FieldError` like:
>
> `Cannot resolve keyword 'sum_total_objs' into field`
>
> Thanks for the support!
New description:
This same QuerySet was working on 4.1.X and prior versions and stopped
working on 4.2.X:
{{{
annotated_users = users_qs.annotate(
total_things=Subquery(
OtherModel.objects.filter(user_id=OuterRef("pk")).annotate(
total_objs=F("total")
).values("total_objs")
)
)
annotated_users.aggregate(
sum_total_objs=Sum("total_things"),
avg_conversion_rate=Case(
When(
sum_total_objs=0,
then=0,
),
default=Round(
(Sum("sum_total_confirmed_objs") /
Sum("sum_total_objs")) * 100, 2
),
output_field=FloatField(),
)
)
}}}
As you can see `sum_total_objs` is an aggregated field that is also used
on a second field to calculate the conversion rate. To avoid a zero
division problem, we were using a Case-When clause over that field. It
works well on any 4.1 and prior versions but stopped working since 4.2,
raising a `FieldError` like:
`Cannot resolve keyword 'sum_total_objs' into field`
The bug is reproducible with an extra test on the django aggregation test
suite:
{{{
def test_referenced_group_by_aggregation_over_annotation(self):
total_books_qs = (
Book.objects.filter(authors__pk=OuterRef("pk"))
.order_by()
.values("pk")
.annotate(total=Count("pk"))
.values("total")
)
annotated_authors = Author.objects.annotate(
total_books=Subquery(total_books_qs.annotate(
total_books=F("total")
).values("total_books")),
total_books_a=Subquery(total_books_qs.filter(
name__istartswith="a"
).annotate(
total_books_a=F("total")
).values("total_books_a")),
).values(
"pk",
"total_books",
"total_books_a",
).order_by("-total_books")
totals = annotated_authors.aggregate(
sum_total_books=Sum("total_books"),
sum_total_books_a=Sum("total_books_a"),
a_over_total_rate=Case(
When(
sum_total_books=0,
then=0,
),
default=Round(
(Sum("total_books_a") / Sum("total_books")) * 100, 2
),
output_field=FloatField(),
),
)
self.assertEqual(totals['sum_total_books'], 3)
self.assertEqual(totals['sum_total_books_a'], 0)
self.assertEqual(totals['a_over_total_rate'], 0)
}}}
Thanks for the support!
--
--
Ticket URL: <https://code.djangoproject.com/ticket/34551#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/01070188001728b6-9bcb5d7e-5cd9-4c1f-ad98-1ff866a01c23-000000%40eu-central-1.amazonses.com.