From e8fe48d3a02dbe3b57a87b334f6335b701f0306d Mon Sep 17 00:00:00 2001 From: lufafajoshua Date: Mon, 25 Sep 2023 13:25:33 +0300 Subject: [PATCH] [4.2.x] Fixed #34808 -- Doc'd aggregate function's default argument. Backport of 8adc7c86ab85ed91e512bc49056e301cbe1715d0 from main --- docs/ref/models/querysets.txt | 18 ++++++++----- docs/topics/db/aggregation.txt | 47 +++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 0d7e9884c3..f58bfa66ed 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -3990,7 +3990,8 @@ by the aggregate. * Default alias: ``__avg`` * Return type: ``float`` if input is ``int``, otherwise same as input - field, or ``output_field`` if supplied + field, or ``output_field`` if supplied. If the queryset or grouping is + empty, ``default`` is returned. .. attribute:: distinct @@ -4028,7 +4029,8 @@ by the aggregate. Returns the maximum value of the given expression. * Default alias: ``__max`` - * Return type: same as input field, or ``output_field`` if supplied + * Return type: same as input field, or ``output_field`` if supplied. If the + queryset or grouping is empty, ``default`` is returned. ``Min`` ~~~~~~~ @@ -4038,7 +4040,8 @@ by the aggregate. Returns the minimum value of the given expression. * Default alias: ``__min`` - * Return type: same as input field, or ``output_field`` if supplied + * Return type: same as input field, or ``output_field`` if supplied. If the + queryset or grouping is empty, ``default`` is returned. ``StdDev`` ~~~~~~~~~~ @@ -4049,7 +4052,8 @@ by the aggregate. * Default alias: ``__stddev`` * Return type: ``float`` if input is ``int``, otherwise same as input - field, or ``output_field`` if supplied + field, or ``output_field`` if supplied. If the queryset or grouping is + empty, ``default`` is returned. .. attribute:: sample @@ -4065,7 +4069,8 @@ by the aggregate. Computes the sum of all values of the given expression. * Default alias: ``__sum`` - * Return type: same as input field, or ``output_field`` if supplied + * Return type: same as input field, or ``output_field`` if supplied. If the + queryset or grouping is empty, ``default`` is returned. .. attribute:: distinct @@ -4082,7 +4087,8 @@ by the aggregate. * Default alias: ``__variance`` * Return type: ``float`` if input is ``int``, otherwise same as input - field, or ``output_field`` if supplied + field, or ``output_field`` if supplied. If the queryset or grouping is + empty, ``default`` is returned. .. attribute:: sample diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index 3cb674aa95..9f98504c0c 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -60,14 +60,16 @@ above: >>> Book.objects.filter(publisher__name="BaloneyPress").count() 73 - # Average price across all books. + # Average price across all books, provide default to be returned instead + # of None if no books exist. >>> from django.db.models import Avg - >>> Book.objects.aggregate(Avg("price")) + >>> Book.objects.aggregate(Avg("price", default=0)) {'price__avg': 34.35} - # Max price across all books. + # Max price across all books, provide default to be returned instead of + # None if no books exist. >>> from django.db.models import Max - >>> Book.objects.aggregate(Max("price")) + >>> Book.objects.aggregate(Max("price", default=0)) {'price__max': Decimal('81.20')} # Difference between the highest priced book and the average price of all books. @@ -632,3 +634,40 @@ aggregate that author count, referencing the annotation field: >>> from django.db.models import Avg, Count >>> Book.objects.annotate(num_authors=Count("authors")).aggregate(Avg("num_authors")) {'num_authors__avg': 1.66} + +Aggregating on empty querysets or groups +---------------------------------------- + +When an aggregation is applied to an empty queryset or grouping, the result +defaults to its :ref:`default ` parameter, typically +``None``. This behavior occurs because aggregate functions return ``NULL`` when +the executed query returns no rows. + +You can specify a return value by providing the :ref:`default +` argument for most aggregations. However, since +:class:`~django.db.models.Count` does not support the :ref:`default +` argument, it will always return ``0`` for empty querysets +or groups. + +For example, assuming that no book contains *web* in its name, calculating the +total price for this book set would return ``None`` since there are no matching +rows to compute the :class:`~django.db.models.Sum` aggregation on: + +.. code-block:: pycon + + >>> from django.db.models import Sum + >>> Book.objects.filter(name__contains="web").aggregate(Sum("price")) + {"price__sum": None} + +However, the :ref:`default ` argument can be set when +calling :class:`~django.db.models.Sum` to return a different default value if +no books can be found: + +.. code-block:: pycon + + >>> Book.objects.filter(name__contains="web").aggregate(Sum("price", default=0)) + {"price__sum": Decimal("0")} + +Under the hood, the :ref:`default ` argument is implemented +by wrapping the aggregate function with +:class:`~django.db.models.functions.Coalesce`.