[Django] #28477: Strip unused annotations from count queries

20 views
Skip to first unread message

Django

unread,
Aug 7, 2017, 6:25:02 PM8/7/17
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom | Owner: nobody
Type: | Status: new
Cleanup/optimization |
Component: Database | Version: master
layer (models, ORM) |
Severity: Normal | Keywords:
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
The query below produces a SQL statement that includes the
Count('chapters'), despite not not being used in any filter operations.

{{{
Book.objects.annotate(Count('chapters')).count()
}}}

It produces the same results as:

{{{
Book.objects.count()
}}}

Django could be more intelligent about what annotations to include in the
query produced by `queryset.count()`, stripping out any annotations that
are not referenced by filters, other annotations or ordering. This should
speed up calls to count() with complex annotations.

There seems to be precedent for this: select_related calls are ignored
with count() queries.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Aug 8, 2017, 2:58:20 PM8/8/17
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom | Owner: nobody
Type: | Status: new
Cleanup/optimization |
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham):

* stage: Unreviewed => Accepted


--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:1>

Django

unread,
Aug 10, 2017, 11:55:31 AM8/10/17
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom | Owner: Tom
Type: | Status: assigned

Cleanup/optimization |
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tom):

* owner: nobody => Tom
* status: new => assigned


--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:2>

Django

unread,
Aug 10, 2017, 11:56:22 AM8/10/17
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom | Owner: Tom
Type: | Status: assigned
Cleanup/optimization |
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Tom):

Same can be done for `exists()`

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:3>

Django

unread,
Sep 5, 2018, 5:15:59 AM9/5/18
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned

Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Tom Forbes):

WIP PR: https://github.com/django/django/pull/8928/files

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:4>

Django

unread,
Sep 5, 2018, 10:20:06 AM9/5/18
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):

* has_patch: 0 => 1


--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:5>

Django

unread,
Sep 11, 2018, 11:36:20 AM9/11/18
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham):

* needs_better_patch: 0 => 1


Comment:

The PR is still marked [WIP] and there are test failures.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:6>

Django

unread,
Feb 27, 2019, 10:57:40 AM2/27/19
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Reupen Shah):

I have also run into problems with `QuerySet.count()` being very slow on
annotated query sets. Django uses a subquery for the count but injects a
group by into the subquery. This typically causes the database server to
deduplicate all matched rows using the group by columns which is, in
general, extremely slow when there are a large number of matched rows.

For example, consider the following model:

{{{
class Person(models.Model):
"""Person model."""

first_name = models.TextField()
last_name = models.TextField()
country = models.TextField(null=True, blank=True)
}}}

and query set:

{{{
from django.db.models.functions import Concat
from django.db.models import Value

queryset = Person.objects.annotate(full_name=Concat('first_name', Value('
'), 'last_name'))
}}}

`queryset.count()` generates the following query under PostgreSQL:

{{{
SELECT COUNT(*)
FROM
(SELECT "support_person"."id" AS Col1,
CONCAT("support_person"."first_name", CONCAT(' ',
"support_person"."last_name")) AS "full_name"
FROM "support_person"
GROUP BY "support_person"."id",
CONCAT("support_person"."first_name", CONCAT(' ',
"support_person"."last_name"))) subquery
}}}

`list(queryset)` generates:

{{{
SELECT "support_person"."id",
"support_person"."first_name",
"support_person"."last_name",
"support_person"."country",
CONCAT("support_person"."first_name", CONCAT(' ',
"support_person"."last_name")) AS "full_name"
FROM "support_person"
}}}


I am not entirely sure why the subquery for the count needs to be any
different from the query used when the query set itself is evaluated.
There are some relevant comments in the source code here:
https://github.com/django/django/blob/5deb7a86e8b54d052a3b1dbed1ae7142d362b1c5/django/db/models/sql/query.py#L404-L414

This has all been tested under Django 2.1.7.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:7>

Django

unread,
Feb 27, 2019, 12:59:06 PM2/27/19
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Simon Charette):

This is somewhat related to #30158 where the compiler is not smart enough
to determine if it can exclude subquery annotations from `GROUP BY` on
aggregation.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:8>

Django

unread,
Mar 4, 2019, 4:05:56 PM3/4/19
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Reupen Shah):

The behaviour is actually a bit more bizarre than I thought.

With the same person model, here are four count variations and the
generated queries:

1.

{{{
Person.objects.count()
}}}

{{{
SELECT COUNT(*) AS "__count" FROM "people_person"
}}}

2.

{{{
Person.objects.values('pk').count()
}}}

{{{
SELECT COUNT(*) AS "__count" FROM "people_person"
}}}

3.

{{{


Person.objects.annotate(full_name=Concat('first_name', Value(' '),

'last_name')).count()
}}}

{{{
SELECT COUNT(*) FROM (SELECT "people_person"."id" AS Col1,
CONCAT("people_person"."first_name", CONCAT(' ',
"people_person"."last_name")) AS "full_name" FROM "people_person" GROUP BY
"people_person"."id", CONCAT("people_person"."first_name", CONCAT(' ',
"people_person"."last_name"))) subquery
}}}

4.

{{{


Person.objects.annotate(full_name=Concat('first_name', Value(' '),

'last_name')).values('pk').count()
}}}

{{{
SELECT COUNT(*) FROM (SELECT "people_person"."id" AS Col1 FROM
"people_person") subquery
}}}


So there's a simple workaround for the case I gave in my last comment in
calling `.values('pk')` before `.count()`. However, that's not much help
if Django or other libraries are the ones calling `.count()`.

On the plus side, I have a better understanding of what the problem is
from looking at the Django source code.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:9>

Django

unread,
Mar 4, 2019, 4:12:19 PM3/4/19
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Reupen Shah):

* cc: Reupen Shah (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:10>

Django

unread,
Mar 23, 2019, 4:59:27 PM3/23/19
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Reupen Shah):

Just a note to say that the behaviour I was describing was rectified in
https://github.com/django/django/pull/11062.

The third case from above now executes the following SQL query:

{{{
SELECT COUNT(*) FROM (
SELECT CONCAT("people_person"."first_name", CONCAT(' ',


"people_person"."last_name")) AS "full_name" FROM "people_person"

) subquery
}}}

Although the original ticket was about eliminating unneeded annotations
completely, the result above is good enough for me, as the group by has
been eliminated which was the real performance killer.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:11>

Django

unread,
Feb 15, 2021, 7:01:55 PM2/15/21
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Tom
Type: | Forbes
Cleanup/optimization | Status: assigned
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by shazad sarwar):

* Attachment "ProductAndServiceUsage.json.zip" added.

Django

unread,
Nov 6, 2022, 2:07:35 AM11/6/22
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev

(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):

* owner: Tom Forbes => Simon Charette


--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:12>

Django

unread,
Nov 6, 2022, 3:55:51 AM11/6/22
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):

* needs_better_patch: 1 => 0


Comment:

I submitted [https://github.com/django/django/pull/16263 a revised PR]
that strip unused annotations not only for `.count()` for but any usage of
`.aggregate`.

To take example from comment:7 and comment:9 the resulting query is now

{{{#!python


Person.objects.annotate(full_name=Concat('first_name', Value(' '),
'last_name')).count()
}}}

{{{#!sql
SELECT COUNT(*) FROM support_person
}}}

The adjusted logic goes along these lines.

Instead of systematically performing a subquery wrapping when there are
existing annotations only do so when the pre-existing annotation are
aggregate or window functions. In both cases, pre-existing
aggregation/window or not, strip annotations that are not referenced by
the aggregates.

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:13>

Django

unread,
Nov 9, 2022, 7:23:13 AM11/9/22
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak):

* stage: Accepted => Ready for checkin


--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:14>

Django

unread,
Nov 9, 2022, 7:53:13 AM11/9/22
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed

Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed

Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak <felisiak.mariusz@…>):

* status: assigned => closed
* resolution: => fixed


Comment:

In [changeset:"59bea9efd2768102fc9d3aedda469502c218e9b7" 59bea9ef]:
{{{
#!CommitTicketReference repository=""
revision="59bea9efd2768102fc9d3aedda469502c218e9b7"
Fixed #28477 -- Stripped unused annotations on aggregation.

Also avoid an unnecessary pushdown when aggregating over a query that
doesn't
have aggregate annotations.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:15>

Django

unread,
Nov 11, 2022, 3:16:42 AM11/11/22
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"a9d2d8d1c36a4338758a792c475965180629a59f" a9d2d8d]:
{{{
#!CommitTicketReference repository=""
revision="a9d2d8d1c36a4338758a792c475965180629a59f"
Refs #28477 -- Reduced complexity of aggregation over qualify queries.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:16>

Django

unread,
Nov 13, 2022, 11:45:46 PM11/13/22
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by GitHub <noreply@…>):

In [changeset:"10037130c123cd747d32a14a9ba47e0c5c9a37d1" 1003713]:
{{{
#!CommitTicketReference repository=""
revision="10037130c123cd747d32a14a9ba47e0c5c9a37d1"
Refs #28477 -- Fixed handling aliased annotations on aggregation.

Just like when using .annotate(), the .alias() method will generate the
necessary JOINs to resolve the alias even if not selected.

Since these JOINs could be multi-valued non-selected aggregates must be
considered to require subquery wrapping as a GROUP BY is required to
combine duplicated tuples from the base table.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:17>

Django

unread,
Apr 7, 2023, 12:58:28 AM4/7/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"9daf8b4109c3e133eb57349bb44d73cc60c5773c" 9daf8b41]:
{{{
#!CommitTicketReference repository=""
revision="9daf8b4109c3e133eb57349bb44d73cc60c5773c"
Fixed #34464 -- Fixed queryset aggregation over group by reference.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks Ian Cubitt for the report.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:18>

Django

unread,
Apr 7, 2023, 12:58:50 AM4/7/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"511dc3db539122577aaba71f5a24d65d5adab092" 511dc3db]:
{{{
#!CommitTicketReference repository=""
revision="511dc3db539122577aaba71f5a24d65d5adab092"
[4.2.x] Fixed #34464 -- Fixed queryset aggregation over group by
reference.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks Ian Cubitt for the report.

Backport of 9daf8b4109c3e133eb57349bb44d73cc60c5773c from main
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:19>

Django

unread,
May 23, 2023, 1:39:49 AM5/23/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4" e5c844d6]:
{{{
#!CommitTicketReference repository=""
revision="e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4"
Fixed #34551 -- Fixed QuerySet.aggregate() crash when referencing
subqueries.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks Denis Roldán and Mariusz for the test.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:20>

Django

unread,
May 23, 2023, 1:40:47 AM5/23/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"c78a4421de0fc3240b91d59e8f9028331777c624" c78a442]:
{{{
#!CommitTicketReference repository=""
revision="c78a4421de0fc3240b91d59e8f9028331777c624"
[4.2.x] Fixed #34551 -- Fixed QuerySet.aggregate() crash when referencing
subqueries.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks Denis Roldán and Mariusz for the test.

Backport of e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4 from main
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:21>

Django

unread,
Jul 5, 2023, 8:26:28 PM7/5/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Dustin Lorres):

Is there a way to opt out of this behavior from application code? After
upgrading from 3.2 to 4.2 some of our pagination stopped working correctly
and it was traced back to `.count()` not providing the correct results,
likely from the updates in this ticket.

Here is an example of the failure (using PostgreSQL JSON query):

{{{
>>> qs = MyModel.objects.annotate(table_element=Func("data",
Value("$.MyArray[*]"),function="jsonb_path_query",output_field=JSONField())).filter(pk=1)
>>> qs.count()
1
>>> len(qs)
2
}}}

This assumes a very simple model `MyModel` with a JSON field `data` that
is has an instance (pk of 1) that has something the data field set to:
{{{
{
"MyArray": [{"id": 1, "name": "test"}, {"id": 2, "name": "test2"}]
}
}}}

The issue is now the `count` is not factoring in the annotation which will
actually increase the number of rows returned in the queryset (for this
example due to the jsonb_path_query which returns a set). It is only
counting the number of rows of `MyModel` which due to the `pk` filter will
only have one row returned.

Is there anyway to force the count operation to expand the query and
include the annotation?

I tried to filter on the count of the jsonb path query, but it failed:
{{{
>>> qs = MyModel.objects.annotate(my_count=Count(Func("data",
Value("$.MyArray[*]"),function="jsonb_path_query",output_field=JSONField()))).filter(pk=1,
my_count__gt=0)
>>> qs.count()
Exception:
django.db.utils.NotSupportedError: set-returning functions are not allowed
in HAVING
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:22>

Django

unread,
Jul 5, 2023, 11:43:42 PM7/5/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Simon Charette):

Dustin, [https://forum.djangoproject.com/t/django-4-2-behavior-change-
when-using-arrayagg-on-unnested-arrayfield-postgresql-specific/21547/2 I
think this answer is your way out].

Basically the optimization should be disabled for any set-returning
function but since Django only has single native one,`Subquery`, so the
optimization is only disabled when the `subquery = True` attribute is set.

In order to truly solve this issue I think we should introduce a new
documented `Expression.set_returning: bool` (better name welcome!) flag
that defaults to `False` but is set to `True` for `Subquery`.

The root of this problem is that the ORM simply doesn't support functions
that return rows in a generic way. Instead it branches out using
`getattr(expr, "subquery", False)` in all cases that it makes the most
sense to support them (e.g. `__in` looups).

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:23>

Django

unread,
Jul 19, 2023, 3:06:11 AM7/19/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"68912e4f6f84f21322f92a2c7b6c77f68f91b9c9" 68912e4]:
{{{
#!CommitTicketReference repository=""
revision="68912e4f6f84f21322f92a2c7b6c77f68f91b9c9"
Fixed #34717 -- Fixed QuerySet.aggregate() crash when referencing window
functions.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks younes-chaoui for the report.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:24>

Django

unread,
Jul 19, 2023, 3:06:43 AM7/19/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"7a67b065d7e5653f3af1cbd28882d33d2a088b02" 7a67b065]:
{{{
#!CommitTicketReference repository=""
revision="7a67b065d7e5653f3af1cbd28882d33d2a088b02"
[4.2.x] Fixed #34717 -- Fixed QuerySet.aggregate() crash when referencing
window functions.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7.

Refs #28477.

Thanks younes-chaoui for the report.

Backport of 68912e4f6f84f21322f92a2c7b6c77f68f91b9c9 from main
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:25>

Django

unread,
Oct 16, 2023, 12:15:05 AM10/16/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"3b4a571275d967512866012955eb0b3ae486d63c" 3b4a5712]:
{{{
#!CommitTicketReference repository=""
revision="3b4a571275d967512866012955eb0b3ae486d63c"
Fixed #34798 -- Fixed QuerySet.aggregate() crash when referencing
expressions containing subqueries.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7,
complements e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4.

Refs #28477, #34551.

Thanks Haldun Komsuoglu for the report.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:26>

Django

unread,
Oct 16, 2023, 12:15:33 AM10/16/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"4ccca9eedc2f453602a20f562399a835a24817c1" 4ccca9ee]:
{{{
#!CommitTicketReference repository=""
revision="4ccca9eedc2f453602a20f562399a835a24817c1"
[5.0.x] Fixed #34798 -- Fixed QuerySet.aggregate() crash when referencing
expressions containing subqueries.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7,
complements e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4.

Refs #28477, #34551.

Thanks Haldun Komsuoglu for the report.

Backport of 3b4a571275d967512866012955eb0b3ae486d63c from main
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:27>

Django

unread,
Oct 16, 2023, 12:15:56 AM10/16/23
to django-...@googlegroups.com
#28477: Strip unused annotations from count queries
-------------------------------------+-------------------------------------
Reporter: Tom Forbes | Owner: Simon
Type: | Charette
Cleanup/optimization | Status: closed
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"803caec60bed3b282b9f9961860a467160c0c8f1" 803caec]:
{{{
#!CommitTicketReference repository=""
revision="803caec60bed3b282b9f9961860a467160c0c8f1"
[4.2.x] Fixed #34798 -- Fixed QuerySet.aggregate() crash when referencing
expressions containing subqueries.

Regression in 59bea9efd2768102fc9d3aedda469502c218e9b7,
complements e5c844d6f2a4ac6ae674d741b5f1fa2a688cedf4.

Refs #28477, #34551.

Thanks Haldun Komsuoglu for the report.

Backport of 3b4a571275d967512866012955eb0b3ae486d63c from main
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/28477#comment:28>

Reply all
Reply to author
Forward
0 new messages