当使用复杂的ORM方式查询时,如果有困惑,使用 str(queryset.query)
查看对应生成的SQL语句。
聚合结果也可以使用过滤。任何应用于普通模型字段的 filter()
(或 exclude()
)对聚合的对象也有约束效果。
当与 annotate()
子句一起使用时,过滤器的效果是限制用来计算“注释”( annotate()
子句会给queryset中的对象增加一个属性和值,称之为“注释”)的对象。
例如,以下查询生成所有标题以 “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()
子句。(因为使用 annotate()
生成的也是一个QuerySet
,根据链式查询的规则,是可以这样的。)
例如,要生成一个具有多位作者的书籍列表:
>>> Book.objects.annotate(num_authors=Count("authors")).filter(num_authors__gt=1)
当开发一个涉及 annotate()
和 filter()
子句的复杂查询时,要特别注意应用于 QuerySet
的子句的顺序。
当一个 annotate()
子句应用于查询,会根据查询状态来计算注释。这实际上意味着 filter()
和 annotate()
不是可交换的操作。
filter()
用在前面,会先过滤对象,再生成注释。
annotate()
用在前面,会先生成注释,然后过滤对象,在多对多的查询和一对多的反向查询时,会容易观察到差异。例如:
>>> a, b = Publisher.objects.annotate(avg_rating=Avg("book__rating")).filter(
... book__rating__gt=3.0
... )
>>> a, a.avg_rating
(<Publisher: A>, 4.5) # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 2.5) # (1+4)/2
>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(
... avg_rating=Avg("book__rating")
... )
>>> a, a.avg_rating
(<Publisher: A>, 4.5) # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 4.0) # 4/1 (book with rating 1 excluded)
第一个查询请求至少有一本评分3以上的书籍的出版者的书籍平均分。第二个查询只请求评分3以上的作者书籍的平均评分。
annotate()
子句之后可以用order_by()
排序,因为注释相当于查询对象结果的一个属性了。
例如,要按参与书籍创作的作者数量对书籍的 QuerySet 进行排序:
>>> Book.objects.annotate(num_authors=Count("authors")).order_by("num_authors")
通常,注解值会添加到每个对象上,即一个被注解的 QuerySet
将会为初始 QuerySet
的每个对象返回一个结果集。然而,当使用 values()
子句来对结果集进行约束时,生成注解值的方法会稍有不同。不是在原始 QuerySet
中对每个对象添加注解并返回,而是根据定义在 values()
子句中的字段组合先对结果进行分组,再对每个单独的分组进行注解,这个注解值是根据分组中所有的对象计算得到的。
通过一个例子对比一下:
查询每个作者所著书的平均评分:
>>> Author.objects.annotate(average_rating=Avg("book__rating"))
使用了values("name")
Author.objects.values("name").annotate(average_rating=Avg("book__rating"))
在这个例子中,作者会按名字分组,所以你只能得到不重名的作者分组后计算出的注释值。如果数据中有两个作者同名,那么他们原本各自的查询结果将被合并到同一个结果中;两个作者的所有评分都将被计算为一个平均分。
和使用 filter()
一样,作用于某个查询的 annotate()
和 values()
子句的顺序非常重要。如果 values()
子句在 annotate()
之前,就会根据 values()
子句产生的分组来计算注解。
然而如果 annotate()
子句在 values()
之前,就会根据整个查询集生成注解。然后 values()
子句只能限制输出的字段。例如:
>>> Author.objects.annotate(average_rating=Avg("book__rating")).values(
... "name", "average_rating"
... )
这段代码将为每个作者添加一个唯一注释,但只有作者姓名和 average_rating
注释会返回在输出结果中。
这里需要注意的一点是order_by()
会影响分组。
你也可以在生成的注释结果上生成聚合。例如,如果您想计算每本书的平均作者数量,您首先要用作者数量对书籍集进行注释,然后对该作者数量进行聚合:
>>> from django.db.models import Avg, Count
>>> Book.objects.annotate(num_authors=Count("authors")).aggregate(Avg("num_authors"))
{'num_authors__avg': 1.66}
当对空的查询集或分组应用聚合操作时,结果通常默认为其 default 参数,通常是 None。这种行为发生是因为当执行的查询不返回任何行时,聚合函数会返回 NULL。