diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 8a4f88a9c8..12a3705674 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -217,6 +217,8 @@ class BaseQuery(object): to return Decimal and long types when they are not needed. """ if value is None: + if aggregate.is_ordinal: + return 0 # Return None as-is return value elif aggregate.is_ordinal: @@ -295,10 +297,14 @@ class BaseQuery(object): query.related_select_cols = [] query.related_select_fields = [] + result = query.execute_sql(SINGLE) + if result is None: + result = [None for q in query.aggregate_select.items()] + return dict([ (alias, self.resolve_aggregate(val, aggregate)) for (alias, aggregate), val - in zip(query.aggregate_select.items(), query.execute_sql(SINGLE)) + in zip(query.aggregate_select.items(), result) ]) def get_count(self): diff --git a/tests/modeltests/aggregation/fixtures/initial_data.json b/tests/modeltests/aggregation/fixtures/initial_data.json index a8f04925f0..0ff26d9ed3 100644 --- a/tests/modeltests/aggregation/fixtures/initial_data.json +++ b/tests/modeltests/aggregation/fixtures/initial_data.json @@ -31,6 +31,14 @@ "num_awards": 9 } }, + { + "pk": 5, + "model": "aggregation.publisher", + "fields": { + "name": "Jonno's House of Books", + "num_awards": 0 + } + }, { "pk": 1, "model": "aggregation.book", diff --git a/tests/modeltests/aggregation/models.py b/tests/modeltests/aggregation/models.py index 4c89d7db3e..a94c475543 100644 --- a/tests/modeltests/aggregation/models.py +++ b/tests/modeltests/aggregation/models.py @@ -194,7 +194,7 @@ u'The Definitive Guide to Django: Web Development Done Right' # Annotate each publisher with the sum of the price of all books sold >>> publishers = Publisher.objects.all().annotate(Sum('book__price')) >>> sorted([(p.name, p.book__price__sum) for p in publishers]) -[(u'Apress', Decimal("59.69")), (u'Morgan Kaufmann', Decimal("75.00")), (u'Prentice Hall', Decimal("112.49")), (u'Sams', Decimal("23.09"))] +[(u'Apress', Decimal("59.69")), (u"Jonno's House of Books", None), (u'Morgan Kaufmann', Decimal("75.00")), (u'Prentice Hall', Decimal("112.49")), (u'Sams', Decimal("23.09"))] # Calls to values() are not commutative over annotate(). @@ -356,7 +356,7 @@ True [] # Aggregates also work on dates, times and datetimes ->>> Publisher.objects.annotate(earliest_book=Min('book__pubdate')).order_by('earliest_book').values() +>>> Publisher.objects.annotate(earliest_book=Min('book__pubdate')).exclude(earliest_book=None).order_by('earliest_book').values() [{'earliest_book': datetime.date(1991, 10, 15), 'num_awards': 9, 'id': 4, 'name': u'Morgan Kaufmann'}, {'earliest_book': datetime.date(1995, 1, 15), 'num_awards': 7, 'id': 3, 'name': u'Prentice Hall'}, {'earliest_book': datetime.date(2007, 12, 6), 'num_awards': 3, 'id': 1, 'name': u'Apress'}, {'earliest_book': datetime.date(2008, 3, 3), 'num_awards': 1, 'id': 2, 'name': u'Sams'}] >>> Store.objects.aggregate(Max('friday_night_closing'), Min("original_opening")) diff --git a/tests/regressiontests/aggregation_regress/fixtures/initial_data.json b/tests/regressiontests/aggregation_regress/fixtures/initial_data.json index a632c4077a..7c5d62c2c6 100644 --- a/tests/regressiontests/aggregation_regress/fixtures/initial_data.json +++ b/tests/regressiontests/aggregation_regress/fixtures/initial_data.json @@ -31,6 +31,14 @@ "num_awards": 9 } }, + { + "pk": 5, + "model": "aggregation_regress.publisher", + "fields": { + "name": "Jonno's House of Books", + "num_awards": 0 + } + }, { "pk": 1, "model": "aggregation_regress.book", diff --git a/tests/regressiontests/aggregation_regress/models.py b/tests/regressiontests/aggregation_regress/models.py index 8a590dca8b..7ff54b2c0d 100644 --- a/tests/regressiontests/aggregation_regress/models.py +++ b/tests/regressiontests/aggregation_regress/models.py @@ -164,6 +164,16 @@ FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, id, i >>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3)) 2 +# Regression for #10089: Check handling of empty result sets with aggregates +>>> Book.objects.filter(id__in=[]).count() +0 + +>>> Book.objects.filter(id__in=[]).aggregate(num_authors=Count('authors'), avg_authors=Avg('authors'), max_authors=Max('authors'), max_price=Max('price'), max_rating=Max('rating')) +{'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None} + +>>> Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values() +[{'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None}] + """ }