Django文档阅读之聚合

聚合

我们将引用以下模型。这些模型用来记录多个网上书店的库存。

from django.db import models

class Author(models.Model): name = models.CharField(max_length=100) age = models.IntegerField() class Publisher(models.Model): name = models.CharField(max_length=300) num_awards = models.IntegerField() class Book(models.Model): name = models.CharField(max_length=300) pages = models.IntegerField() price = models.DecimalField(max_digits=10, decimal_places=2) rating = models.FloatField() authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) pubdate = models.DateField() class Store(models.Model): name = models.CharField(max_length=300) books = models.ManyToManyField(Book) registered_users = models.PositiveIntegerField() 

速查表

下面是根据以上模型执行常见的聚合查询:

# Total number of books.
>>> Book.objects.count() 2452 # Total number of books with publisher=BaloneyPress >>> Book.objects.filter(publisher__name='BaloneyPress').count() 73 # Average price across all books. >>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} # Max price across all books. >>> from django.db.models import Max >>> Book.objects.all().aggregate(Max('price')) {'price__max': Decimal('81.20')} # Difference between the highest priced book and the average price of all books. >>> from django.db.models import FloatField >>> Book.objects.aggregate( ... price_diff=Max('price', output_field=FloatField()) - Avg('price')) {'price_diff': 46.85} # All the following queries involve traversing the Book<->Publisher # foreign key relationship backwards. # 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')) >>> pubs <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]> >>> pubs[0].num_books 73 # Each publisher, with a separate count of books with a rating above and below 5 >>> from django.db.models import Q >>> above_5 = Count('book', filter=Q(book__rating__gt=5)) >>> below_5 = Count('book', filter=Q(book__rating__lte=5)) >>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5) >>> pubs[0].above_5 23 >>> pubs[0].below_5 12 # The top 5 publishers, in order by number of books. >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5] >>> pubs[0].num_books 1323

QuerySet上生成聚合

Django提供了两种生成聚合的方法。第一种方法是从整个QuerySet生成汇总值。比如你想要计算所有在售书的平均价格.Django的查询语法提供了一种用来描述所有图书集合的方法:

>>> Book.objects.all() 

通过可以在QuerySet后添加aggregate()[主语]来计算QuerySet对象的汇总值。

>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35} 

all()是在本例中多余的,所以这可以简化为:

>>> Book.objects.aggregate(Avg('price')) {'price__avg': 34.35} 

aggregate()子句的参数描述了我们想要计算的聚合值 - 在本例中,priceBook模型上字段 的平均值

 
   

aggregate()是一个终结子句QuerySet,当被调用时,返回一个名称 - 值对的字典。名称是聚合值的标识符; 该值是计算的聚合。该名称是从字段名称和聚合函数自动生成的。如果要手动指定聚合值的名称,可以通过在指定聚合子句时提供该名称来实现:

 
   
>>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35} 
 
   

如果要生成多个聚合,只需在该aggregate()子句中添加另一个参数即可因此,如果我们还想了解所有图书的最高和最低价格,我们会发出查询:

 
   
>>> from django.db.models import Avg, Max, Min >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

可以使用该annotate()子句生成每对象摘要当一个annotate()指定的子句,在每个对象QuerySet 将具有指定值进行注释。

这些注释的语法与用于该aggregate()子句的语法相同 每个参数annotate()描述要计算的聚合。例如,要注释具有作者数量的书籍:

# Build an annotated queryset
>>> from django.db.models import Count >>> q = Book.objects.annotate(Count('authors')) # Interrogate the first object in the queryset >>> q[0] <Book: The Definitive Guide to Django> >>> q[0].authors__count 2 # Interrogate the second object in the queryset >>> q[1] <Book: Practical Django Projects> >>> q[1].authors__count 1

当指定要在聚合函数中聚合的字段时,Django将允许您使用在引用过滤器中的相关字段时使用的相同双下划线表示法然后Django将处理检索和聚合相关值所需的任何表连接。

例如,要查找每个商店中提供的书籍的价格范围,您可以使用注释:

>>> from django.db.models import Max, Min >>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price')) 

这告诉Django检索Store模型,模型连接(通过多对多关系)Book,并在书模型的价格字段上聚合以产生最小值和最大值。

同样的规则适用于该aggregate()条款。如果您想知道任何商店中可供出售的任何书籍的最低价格和最高价格,您可以使用聚合:

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))
 
       

我们可以要求所有发布者注释其各自的总账簿计数器(注意我们如何使用'book'指定 Publisher- > Book反向外键跳):

 
       
>>> from django.db.models import Avg, Count, Min, Sum >>> Publisher.objects.annotate(Count('book')) 
 
       

(每次Publisher在由此而来QuerySet将有一个名为额外属性book__count。)

 
       

我们还可以要求每个出版商管理的最老的书籍:

 
       
>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate')) 
 
       

(生成的字典会有一个名为'oldest_pubdate'。如果没有指定这样的别名,那就相当长了'book__pubdate__min'。)

 
       

filter()exclude()

 
       

聚合也可以参与过滤器。应用于普通模型字段的任何filter()(或 exclude())将具有约束考虑进行聚合的对象的效果。

 
       

annotate()子句一起使用时,过滤器具有约束计算注释的对象的效果。例如,您可以使用查询生成带有标题以“Django”开头的所有书籍的带注释的列表:

 
       
>>> from django.db.models import Avg, Count >>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors')) 
 
       

aggregate()子句一起使用时,过滤器具有约束计算聚合的对象的效果。例如,您可以使用查询生成标题以“Django”开头的所有书籍的平均价格:

 
       
>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))

过滤注释

也可以过滤带注释的值。注释的别名可以使用filter()exclude()子句以与任何其他模型字段相同的方式使用。

例如,要生成包含多个作者的书籍列表,您可以发出查询:

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1) 

此查询生成带注释的结果集,然后基于该批注生成过滤器。

如果需要两个带有两个单独过滤器的注释,则可以将 filter参数与任何聚合一起使用。例如,要生成具有高评价书籍数量的作者列表:

>>> highly_rated = Count('books', filter=Q(books__rating__gte=7)) >>> Author.objects.annotate(num_books=Count('books'), highly_rated_books=highly_rated) 

Author结果集中的每个都将具有num_books和 highly_rated_books属性。

 
       

order_by()

 
       

注释可以用作排序的基础。定义order_by()子句时,您提供的聚合可以引用annotate()在查询中定义为子句一部分的任何别名

 
       

例如,要按照QuerySet为图书贡献的作者数量订购图书,您可以使用以下查询:

 
       
>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
 
       

values()

 
       

通常,注解值会添加到每个对象上,即被一个注解的QuerySet将会为初始QuerySet的每个对象报道查看一个查询查询结果集。然而,使用当values()¸...。来对查询查询结果集进行约束时,生成注解值的方法会稍有不同。在不是原始QuerySet中对每个对象添加注解并报道查看,根据而是定义在values()[主语]中的字段组合先对查询查询结果进行分组,再对每个单独的分组进行注解,这个注解值是根据分组中所有的对象计算得到的。

 
       

下面是一个关于作者的查询例子,查询每个作者所着书的平均评分:

 
       
>>> Author.objects.annotate(average_rating=Avg('book__rating')) 
 
       

这段代码返回的是数据库中的所有作者及其所着书的平均评分。

 
       

如果但是你使用values()[主语],结果会稍有不同:

 
       
>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating')) 
 
       

在这个例子中,作者会按名字分组,所以你只能得到不重名的作者分组的注解值。这意味着如果你有两个作者同名,那么他们原本各自的查询结果将被合并到同一个结果中;两个作者的所有评分都将被计算为一个平均分。

 
       

annotate()状语从句:values()的顺序

 
       

使用状语从句:filter()一样,于作用英文查询某个的annotate()状语从句:values()[主语]的顺序非常重要。如果values()[主语]在annotate()之前,根据就会values()[主语]产生的分组来计算注解。

 
       

如果然而annotate()[主语]在values()之前,就会根据整个查询集生成注解。这种情况下,values()子句只能限制输出的字段。

 
       

举个例子,我们如果颠倒上个例子中values()状语从句:annotate()的顺序:

 
       
>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating') 
 
       

这段代码将为每个作者添加一个唯一注解,只有但作者姓名状语从句:average_rating注解会报道查看在输出查询查询结果中。

 
       

您还应注意,average_rating已明确包含在要返回的值列表中。由于values()annotate()子句的排序,这是必需的

 
       

如果该values()子句在子句之前annotate(),则任何注释都将自动添加到结果集中。但是,如果在values() 子句之后应用该annotate()子句,则需要显式包含聚合列。

 


 

猜你喜欢

转载自www.cnblogs.com/roygood/p/10121834.html