From 0560bfb705687c831e2769b1202706e2ceb1f7a7 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Mon, 11 Feb 2013 12:49:30 -0300 Subject: [PATCH] Mention backward relationships in aggregate docs. Thanks Anssi and Marc Tamlyn for reviewing. Fixes #19803. --- docs/topics/db/aggregation.txt | 53 +++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index 3a4d287864..49134e24c0 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -21,14 +21,12 @@ used to track the inventory for a series of online bookstores: class Author(models.Model): name = models.CharField(max_length=100) age = models.IntegerField() - friends = models.ManyToManyField('self', blank=True) class Publisher(models.Model): name = models.CharField(max_length=300) num_awards = models.IntegerField() class Book(models.Model): - isbn = models.CharField(max_length=9) name = models.CharField(max_length=300) pages = models.IntegerField() price = models.DecimalField(max_digits=10, decimal_places=2) @@ -40,6 +38,7 @@ used to track the inventory for a series of online bookstores: class Store(models.Model): name = models.CharField(max_length=300) books = models.ManyToManyField(Book) + registered_users = models.PositiveIntegerField() Cheat sheet =========== @@ -64,6 +63,9 @@ In a hurry? Here's how to do common aggregate queries, assuming the models above >>> Book.objects.all().aggregate(Max('price')) {'price__max': Decimal('81.20')} + # All the following queries involve traversing the Book<->Publisher + # many-to-many relationship backward + # Each publisher, each with a count of books as a "num_books" attribute. >>> from django.db.models import Count >>> pubs = Publisher.objects.annotate(num_books=Count('book')) @@ -73,7 +75,6 @@ In a hurry? Here's how to do common aggregate queries, assuming the models above 73 # The top 5 publishers, in order by number of books. - >>> from django.db.models import Count >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5] >>> pubs[0].num_books 1323 @@ -169,7 +170,7 @@ specify the annotation:: Unlike ``aggregate()``, ``annotate()`` is *not* a terminal clause. The output of the ``annotate()`` clause is a ``QuerySet``; this ``QuerySet`` can be modified using any other ``QuerySet`` operation, including ``filter()``, -``order_by``, or even additional calls to ``annotate()``. +``order_by()``, or even additional calls to ``annotate()``. Joins and aggregates ==================== @@ -205,6 +206,50 @@ issue the query:: >>> Store.objects.aggregate(youngest_age=Min('books__authors__age')) +Following relationships backwards +--------------------------------- + +In a way similar to :ref:`lookups-that-span-relationships`, aggregations and +annotations on fields of models or models that are related to the one you are +querying can include traversing "reverse" relationships. The lowercase name +of related models and double-underscores are used here too. + +For example, we can ask for all publishers, annotated with their respective +total book stock counters (note how we use `'book'` to specify the +Publisher->Book reverse foreign key hop):: + + >>> from django.db.models import Count, Min, Sum, Max, Avg + >>> Publisher.objects.annotate(Count('book')) + +(Every Publisher in the resulting QuerySet will have an extra attribute called +``book__count``.) + +We can also ask for the oldest book of any of those managed by every publisher:: + + >>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate')) + +(The resulting dictionary will have a key called ``'oldest_pubdate'``. If no +such alias was specified, it would be the rather long ``'book__pubdate__min'``.) + +This doesn't apply just to foreign keys. It also works with many-to-many +relations. For example, we can ask for every author, annotated with the total +number of pages considering all the books he/she has (co-)authored (note how we +use `'book'` to specify the Author->Book reverse many-to-many hop):: + + >>> Author.objects.annotate(total_pages=Sum('book__pages')) + +(Every Author in the resulting QuerySet will have an extra attribute called +``total_pages``. If no such alias was specified, it would be the rather long +``book__pages__sum``.) + +Or ask for the average rating of all the books written by author(s) we have on +file:: + + >>> Author.objects.aggregate(average_rating=Avg('book__rating')) + +(The resulting dictionary will have a key called ``'average__rating'``. If no +such alias was specified, it would be the rather long ``'book__rating__avg'``.) + Aggregations and other QuerySet clauses =======================================