1
0
mirror of https://github.com/django/django.git synced 2025-07-07 11:19:12 +00:00

Refs #28477 -- Reduced complexity of aggregation over qualify queries.

This commit is contained in:
Simon Charette 2022-11-09 21:55:47 -05:00 committed by Mariusz Felisiak
parent 99b4f90ec6
commit a9d2d8d1c3
2 changed files with 34 additions and 22 deletions

View File

@ -447,13 +447,15 @@ class Query(BaseExpression):
if alias not in added_aggregate_names if alias not in added_aggregate_names
} }
# Existing usage of aggregation can be determined by the presence of # Existing usage of aggregation can be determined by the presence of
# selected aggregate and window annotations but also by filters against # selected aggregates but also by filters against aliased aggregates.
# aliased aggregate and windows via HAVING / QUALIFY. _, having, qualify = self.where.split_having_qualify()
has_existing_aggregation = any( has_existing_aggregation = (
any(
getattr(annotation, "contains_aggregate", True) getattr(annotation, "contains_aggregate", True)
or getattr(annotation, "contains_over_clause", True)
for annotation in existing_annotations.values() for annotation in existing_annotations.values()
) or any(self.where.split_having_qualify()[1:]) )
or having
)
# Decide if we need to use a subquery. # Decide if we need to use a subquery.
# #
# Existing aggregations would cause incorrect results as # Existing aggregations would cause incorrect results as
@ -468,6 +470,7 @@ class Query(BaseExpression):
isinstance(self.group_by, tuple) isinstance(self.group_by, tuple)
or self.is_sliced or self.is_sliced
or has_existing_aggregation or has_existing_aggregation
or qualify
or self.distinct or self.distinct
or self.combinator or self.combinator
): ):
@ -494,8 +497,11 @@ class Query(BaseExpression):
self.model._meta.pk.get_col(inner_query.get_initial_alias()), self.model._meta.pk.get_col(inner_query.get_initial_alias()),
) )
inner_query.default_cols = False inner_query.default_cols = False
if not qualify:
# Mask existing annotations that are not referenced by # Mask existing annotations that are not referenced by
# aggregates to be pushed to the outer query. # aggregates to be pushed to the outer query unless
# filtering against window functions is involved as it
# requires complex realising.
annotation_mask = set() annotation_mask = set()
for name in added_aggregate_names: for name in added_aggregate_names:
annotation_mask.add(name) annotation_mask.add(name)

View File

@ -42,6 +42,7 @@ from django.db.models.functions import (
) )
from django.db.models.lookups import Exact from django.db.models.lookups import Exact
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext
from .models import Classification, Detail, Employee, PastEmployeeDepartment from .models import Classification, Detail, Employee, PastEmployeeDepartment
@ -1157,6 +1158,7 @@ class WindowFunctionTests(TestCase):
) )
def test_filter_count(self): def test_filter_count(self):
with CaptureQueriesContext(connection) as ctx:
self.assertEqual( self.assertEqual(
Employee.objects.annotate( Employee.objects.annotate(
department_salary_rank=Window( department_salary_rank=Window(
@ -1167,6 +1169,10 @@ class WindowFunctionTests(TestCase):
.count(), .count(),
5, 5,
) )
self.assertEqual(len(ctx.captured_queries), 1)
sql = ctx.captured_queries[0]["sql"].lower()
self.assertEqual(sql.count("select"), 3)
self.assertNotIn("group by", sql)
@skipUnlessDBFeature("supports_frame_range_fixed_distance") @skipUnlessDBFeature("supports_frame_range_fixed_distance")
def test_range_n_preceding_and_following(self): def test_range_n_preceding_and_following(self):