---
title: "聚合"
version: 4.0
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/4.0/topics/db/aggregation/
canonical: https://djangodocs.dev/zh-hans/4.0/topics/db/aggregation/
---
# 聚合

Django 数据库抽象 API 描述了使用 Django queries 来增删查改单个对象的方法。 然而，有时候你要获取的值需要根据一组对象聚合后才能得到。这个主题指南描述了如何使用 Django queries 来生成和返回聚合值的方法。

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

```python
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)

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)
```

## 速查表

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

```
# 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()` 的参数描述了我们想要计算的聚合值。在这个例子里，要计算的就是 `Book` 模型上的 `price` 字段的平均值。可用的聚合函数列表可以在 [QuerySet reference](/zh-hans/4.0/ref/models/querysets/#aggregation-functions) 中找到。

`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')}
```

## 为 `QuerySet` 中的每一个条目生成聚合

生成值的汇总的另一个办法是为 [`QuerySet`](/zh-hans/4.0/ref/models/querysets/#django.db.models.query.QuerySet) 的每一个对象生成独立汇总。比如，如果你想检索书籍列表，你可能想知道每一本书有多少作者。每一本书与作者有多对多的关系；我们想在 `QuerySet` 中为每一本书总结这个关系。

使用 [`annotate()`](/zh-hans/4.0/ref/models/querysets/#django.db.models.query.QuerySet.annotate) 子句可以生成每一个对象的汇总。当指定  `annotate()` 子句，`QuerySet` 中的每一个对象将对指定值进行汇总。

这些汇总语法规则与 [`aggregate()`](/zh-hans/4.0/ref/models/querysets/#django.db.models.query.QuerySet.aggregate) 子句的规则相同。`annotate()` 的每一个参数描述了一个要计算的聚合。比如，注解（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
```

与 `aggregate()` 一样，注解的名称是根据聚合函数和被聚合的字段名自动生成的。当你在指定注解的时候，你可以通过提供一个别名重写这个默认名：

```
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
```

与 `aggregate()` 不同的是，`annotate()` 不是终端子句。`annotate()` 子句的输出就是 `QuerySet`；这个 `QuerySet` 被其他 `QuerySet` 操作进行修改，包括 filter()\`, `order_by()` ，甚至可以对 `annotate()` 进行额外调用。

### 组合多个聚合

使用 `annotate()` 组合多个聚合将产生错误的结果( [yield the wrong results](https://code.djangoproject.com/ticket/10060) )，因为它使用连接(joins)而不是子查询：

```
>>> book = Book.objects.first()
>>> book.authors.count()
2
>>> book.store_set.count()
3
>>> q = Book.objects.annotate(Count('authors'), Count('store'))
>>> q[0].authors__count
6
>>> q[0].store__count
6
```

对大部分聚合来说，没办法避免这个问题，但是，[`Count`](/zh-hans/4.0/ref/models/querysets/#django.db.models.Count) 聚合可以使用 `distinct` 参数来避免：

```
>>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True))
>>> q[0].authors__count
2
>>> q[0].store__count
3
```

> **如有疑问，请检查 SQL 查询！**
>
> 为了搞清楚你的查询发生了什么问题，你得在 `QuerySet` 中检查一下\`\`query\`\` 属性。

## 连接(Joins)和聚合

到目前为止，我们已经处理了被查询模型字段的聚合。然而，有时候想聚合的值属于你正在查询模型的关联模型。

在聚合函数里面指定聚合的字段时，Django 允许你在过滤相关字段的时候使用相同的双下划线表示法。Django 将处理任何需要检索和聚合的关联值的表连接(table joins)。

比如，要寻找每个书店提供的书籍价格区间，你可以使用这个注解(annotation)：

```
>>> 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'))
```

Join 链可以根据你的需求尽可能深。比如，要提取所出售的书籍中最年轻的作者年龄，你可以写这样的查询：

```
>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
```

### 反向关系

类似于 [跨关系查询](/zh-hans/4.0/topics/db/queries/#lookups-that-span-relationships) ，你正在查询的在模型和模型字段上的聚合和注解(annotations)可以包含反向关系。关系模型的小写名和双下划线也可以用在这里。

比如我们可以要求所有出版者，注解他们各自的图书库存总数（注意我们如何使用 `'book'` 来指定 `Publisher` -\> `Book` 反向外键跳转）：

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

(查询结果里的每一个 `Publisher` 会有多余的属性—— `book__count` 。)

我们也可以找出最古老的一本：

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

(结果字典中会有一个叫 `'oldest_pubdate'` 的键。如果没有指定这样的别名，它将会是一个很长的名字 `'book__pubdate__min'` 。)

它不仅仅用于外键，它也适用于多对多关系。比如，我们能查询每一个作者，注解作者(共同)创作的书籍总页数(注意我们如何使用 `'book'` 来指定 `Author` -\> `Book` 反向多对多跳转)：

```
>>> Author.objects.annotate(total_pages=Sum('book__pages'))
```

（结果集里的每一个 `Author` 会有一个额外的属性——`total_pages`）如果没有指定这样的别名，它将会是一个很长的名字 `book__pages__sum`）

或查询书籍的平均评分：

```
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
```

（结果字典会有一个叫 `'average_rating'` 的键。如果没有指定这样的别名，它将会是一个很长的名字 `'book__rating__avg'`。）

## 聚合和其他 `QuerySet` 子句

### `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('book', filter=Q(book__rating__gte=7))
>>> Author.objects.annotate(num_books=Count('book'), highly_rated_books=highly_rated)
```

结果集中的每个 `Author` 都有 `num_books` 和 `highly_rated_books` 属性。参见 [条件聚合](/zh-hans/4.0/ref/models/conditional-expressions/#conditional-aggregation)。

> **在 filter 和 QuerySet.filter() 中做选择**
>
> 避免在单个注解和聚合中使用 `filter` 语句。使用 `QuerySet.filter()` 来排除列会很高效。聚合 `filter` 语句只在使用具有不同条件的相同关系的两个或以上的聚合时有用。

#### `annotate()` 和 `filter()` 子句的顺序

当开发一个涉及 `annotate()` 和 `filter()` 子句的复杂查询时，要特别注意应用于  `QuerySet` 的子句的顺序。

当一个 `annotate()` 子句应用于查询，会根据查询状态来计算注解，直到请求的注解为止。这实际上意味着 `filter()` 和 `annotate()` 不是可交换的操作。

比如：

- 出版者A有两本评分4和5的书。
- 出版者B有两本评分1和4的书。
- 出版者C有一本评分1的书。

下面就是 `Count` 聚合的例子：

```
>>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 2)

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 1)
```

两个查询返回出版者列表，这些出版者至少有一本评分3的书，因此排除了C。

在第一个查询里，注解优先于过滤器，因此过滤器没有影响注解。`distinct=True` 用来避免 a [query bug](#combining-multiple-aggregations)。

第二个查询每个发布者评分3以上的书籍数量。过滤器优先于注解，因此过滤器约束计算注解时考虑的对象。

这里是另一个关于 `Avg` 聚合的例子：

```
>>> 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以上的作者书籍的平均评分。

很难凭直觉了解ORM如何将复杂的查询集转化为SQL查询，因此当有疑问时，请使用 str(queryset.query)\` 检查SQL，并写大量的测试。

### `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()` 子句之后，则需要显式包含聚合列。

#### Interaction with `order_by()`

Fields that are mentioned in the `order_by()` part of a queryset are used
when selecting the output data, even if they are not otherwise specified in the
`values()` call. These extra fields are used to group "like" results together
and they can make otherwise identical result rows appear to be separate. This
shows up, particularly, when counting things.

举个例子，假设你有这样的模型：

```
from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=10)
    data = models.IntegerField()
```

If you want to count how many times each distinct `data` value appears in an
ordered queryset, you might try this:

```
items = Item.objects.order_by('name')
# Warning: not quite correct!
items.values('data').annotate(Count('id'))
```

...which will group the `Item` objects by their common `data` values and
then count the number of `id` values in each group. Except that it won't
quite work. The ordering by `name` will also play a part in the grouping, so
this query will group by distinct `(data, name)` pairs, which isn't what you
want. Instead, you should construct this queryset:

```
items.values('data').annotate(Count('id')).order_by()
```

清除任何查询中的排序。你也可以通过 `data` 排序，没有任何有害影响，因为它已经在查询中发挥了作用。

这个行为与 [`distinct()`](/zh-hans/4.0/ref/models/querysets/#django.db.models.query.QuerySet.distinct) 的查询文档指出的行为相同，一般规则是一样的：通常情况下，你不希望额外的列在结果中发挥作用，因此要清除排序，或者至少确保它只限于您在 `values()` 调用中选择的那些字段。

> **Note**
>
> 你可以会问为什么 Django 没有移除无关的列。主要原因就是与 `distinct()` 和其他地方的一致性:Django从不删除你指定的排序约束（我们不能改变其他方法的行为，因为这会违反我们的应用编程接口的稳定性政策）。

### 聚合注解

你也可以在注解结果上生成聚合。当你定义 `aggregate()` 子句时，你提供的聚合可以引用任何定义在查询中 `annotate()` 子句的别名。

比如，如果你想计算每本书的平均作者数，首先使用作者数注解书籍集合，然后引用注解字段聚合作者数：

```
>>> from django.db.models import Avg, Count
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}
```
