【Django】聚合查询

本篇以下面的模型为基础进行讨论,根据查询目标列出示例代码,和示例结果。

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)

count()

查询图书总数

python 复制代码
>>> Book.objects.count()
2452

出版社名为"WordPress"的图书总数

python 复制代码
>>> Book.objects.filter(publisher__name="WordPress").count()
73

aggregate()

aggregate()方法是在整个 QuerySet 上生成聚合值。

所有书的均价,如果没有书则返回默认值0

python 复制代码
>>> from django.db.models import Avg
>>> Book.objects.aggregate(Avg("price", default=0))
{'price__avg': 34.35}

所有书中最贵的价格,如果没有书则返回默认值0

python 复制代码
>>> from django.db.models import Max
>>> Book.objects.aggregate(Max("price", default=0))
{'price__max': Decimal('81.20')}

aggregate() 是一个 QuerySet 的终端子句,当调用时,它返回一个字典。名称是聚合值的标识符;值是计算得到的聚合值。名称是从字段名称和聚合函数自动生成的。如果您想手动指定聚合值的名称,可以在指定聚合子句时提供该名称:

python 复制代码
>>> Book.objects.aggregate(average_price=Avg("price"))
{'average_price': 34.35}

如果您想生成多个聚合值,可以向 aggregate() 子句添加另一个参数。因此,如果我们还想知道所有书的最高价和最低价,可以发出以下查询:

python 复制代码
>>> 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')}

书中最贵价格和平均价格的差值

python 复制代码
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
...     price_diff=Max("price", output_field=FloatField()) - Avg("price")
... )
{'price_diff': 46.85}

annotate()

使用 annotate() 子句可以生成每一个对象的汇总。当指定 annotate() 子句,QuerySet 中的每一个对象将对指定值进行汇总。

aggregate() 相同点,注释的名称是从聚合函数的名称和被聚合字段的名称自动派生的。您可以通过在指定注释时提供别名来覆盖这个默认名称。

aggregate() 不同的是,annotate() 不是终端子句。annotate() 子句的输出就是 QuerySet;这个 QuerySet 可以像其他 QuerySet 一样进行操作,包括 filter(), order_by() ,甚至可以对 annotate() 进行额外调用。

查询每个出版社的图书总数,并添加到"num_books"属性值中

python 复制代码
>>> 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

查询每个出版社评分大于5的书数量和小于等于5的书的数量,并添加到"above_5"和"below_5"的属性值中

python 复制代码
>>> 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

找出书籍数量排名前5的出版社,并添加到"num_books"属性值中

python 复制代码
>>> pubs = Publisher.objects.annotate(num_books=Count("book")).order_by("-num_books")[:5]
>>> pubs[0].num_books
1323

还有个地方需要注意:使用 annotate() 组合多个聚合将产生错误的结果,因为它使用连接(joins)而不是子查询,下面是一个错误示例:

python 复制代码
>>> 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 聚合可以使用 distinct 参数来避免:

python 复制代码
>>> q = Book.objects.annotate(
...     Count("authors", distinct=True), Count("store", distinct=True)
... )
>>> q[0].authors__count
2
>>> q[0].store__count
3
相关推荐
今晚务必早点睡16 小时前
微服务改数据库密码后服务仍能访问?一次“看似异常、实则常见”的生产现象全解析
数据库·微服务·oracle
老师我太想进步了202618 小时前
cmd连接MySQL及相关查询
数据库·mysql
難釋懷20 小时前
Redis命令-Set命令
数据库·redis·缓存
Linux-palpitate21 小时前
PostgreSQL(PG)的1主2从集群部署安装
数据库·postgresql
heartbeat..21 小时前
数据库基础知识体系:概念、约束、范式与国产产品
java·数据库·学习笔记·国产数据库
山峰哥1 天前
数据库工程核心:SQL调优让查询效率飙升的实战密码
网络·汇编·数据库·sql·编辑器
Coder_Boy_1 天前
基于SpringAI的在线考试系统-DDD业务领域模块设计思路
java·数据库·人工智能·spring boot·ddd
小雪_Snow1 天前
Windows 安装 MySQL 8.0 教程【安装包方式】
数据库·mysql
无敌的牛1 天前
MySQL初阶
数据库·mysql
不会C++的雾1 天前
Linux操作系统(2)
linux·数据库·mysql