Django ORM – 多表实例(聚合与分组查询)(建议收藏)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言

在 Django 开发中,ORM(对象关系映射)是连接 Python 代码与数据库的核心工具。随着项目复杂度的提升,开发者常常需要处理多表关联、数据聚合与分组查询等场景。例如,统计用户发布的文章数量、按分类汇总订单金额,或是分析不同部门的销售数据。本文将通过实例深入讲解 Django ORM 在多表操作中的聚合与分组查询,帮助开发者高效管理复杂数据逻辑。


一、基础概念与多表关系

1.1 ORM 的核心思想

Django ORM 允许开发者通过 Python 代码直接操作数据库,无需编写原始 SQL 语句。其核心是将数据库表映射为 Python 类(模型),通过模型方法实现增删改查。例如,一个 Post 表对应 Post 模型,每个字段(如 titlecontent)对应模型的属性。

1.2 多表关联类型

Django 支持三种多表关系:

  • 一对一关系OneToOneField):例如用户与个人资料表。
  • 多对一关系ForeignKey):例如文章与分类表,一篇文章属于一个分类。
  • 多对多关系ManyToManyField):例如用户与标签表,一个用户可以有多个标签,一个标签也可以关联多个用户。

比喻:可以想象数据库是一个图书馆,每个表是书架,字段是书籍。多表关系就像不同书架之间的关联:一本书(Post)只能放在一个分类书架(Category)上(多对一),而用户(User)可以同时出现在多个标签书架(多对多)。


二、聚合查询(Aggregation)

聚合查询用于从数据集中提取统计信息,如总数、平均值、最大值等。Django 提供了 aggregate() 方法实现这一功能。

2.1 常用聚合函数

常见的聚合函数包括:
| 函数 | 作用 | 示例 |
|---------------|--------------------------|--------------------------|
| Count() | 统计记录数量 | Post.objects.count() |
| Sum() | 求和 | Order.objects.sum('price') |
| Avg() | 计算平均值 | Score.objects.avg('value') |
| Max()/Min() | 获取最大/小值 | Sale.objects.max('revenue') |

2.2 实际案例:统计用户发布的文章数

假设有一个 Post 模型,包含 author 字段(外键关联用户):

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.TextField()

要统计某个用户发布的文章总数,可以使用:

user = User.objects.get(id=1)
total_posts = Post.objects.filter(author=user).aggregate(total=Count('id'))
print(total_posts)  # 输出:{'total': 5}

注意aggregate() 返回一个字典,键为自定义的别名(如 total),值为计算结果。


三、分组查询(Group By)

分组查询通过 annotate() 方法将聚合结果与原始数据关联,常用于按某一字段分组并统计。

3.1 annotate() 的基本用法

annotate() 会为每个查询结果对象附加一个聚合字段。例如,统计每个分类下的文章数量:

from django.db.models import Count

categories = Category.objects.annotate(post_count=Count('post'))
for category in categories:
    print(f"分类:{category.name},文章数:{category.post_count}")

比喻:这就像把图书馆的书籍按书架分类,每个分类标签上显示该分类的书籍总数。

3.2 结合过滤条件与排序

分组查询常需配合过滤和排序:

categories = (
    Category.objects
    .annotate(post_count=Count('post'))
    .filter(post_count__gte=10)
    .order_by('-post_count')
)

四、多表关联案例:用户与订单分析

4.1 模型设计

假设有一个电商场景,包含以下模型:

class User(models.Model):
    name = models.CharField(max_length=50)

class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    product = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)

4.2 用户订单金额统计

要统计每个用户的总消费金额:

users = User.objects.annotate(total_spent=Sum('order__price'))
for user in users:
    print(f"用户:{user.name},总消费:{user.total_spent}")

关键点:通过双下划线 __ 表示外键关联字段(order__price),Django 会自动处理跨表查询。

4.3 分组与条件过滤

若需统计某段时间内每个用户的订单数:

from datetime import datetime

start_date = datetime(2023, 1, 1)
users = (
    User.objects
    .filter(order__created_at__gte=start_date)
    .annotate(order_count=Count('order'))
    .order_by('-order_count')
)

五、性能优化与注意事项

5.1 避免 N+1 查询问题

分组查询时,若直接遍历对象的关联属性(如 user.order_set.all()),会导致多次数据库查询。此时应使用 select_related()(单表关联)或 prefetch_related()(多表关联):

users = User.objects.prefetch_related('order_set').all()
for user in users:
    orders = user.order_set.all()  # 不会触发额外查询

5.2 索引优化

对频繁查询的字段(如 order__created_at)添加数据库索引,可显著提升性能:

class Order(models.Model):
    created_at = models.DateTimeField(db_index=True)  # 添加索引

六、高级技巧:跨表聚合与条件分组

6.1 使用 CaseWhen 实现条件统计

假设要统计用户订单中,价格高于 100 元的订单数量:

from django.db.models import Case, When, IntegerField

users = User.objects.annotate(
    expensive_orders=Count(
        Case(
            When(order__price__gt=100, then=1),
            output_field=IntegerField()
        )
    )
)

6.2 跨多表的复杂分组

例如,统计每个用户在不同月份的订单金额:

from django.db.models.functions import TruncMonth

orders = (
    Order.objects
    .annotate(month=TruncMonth('created_at'))
    .values('user__name', 'month')
    .annotate(total=Sum('price'))
    .order_by('month')
)

结论

通过 Django ORM 的聚合与分组查询功能,开发者可以高效处理多表数据的统计与分析需求。本文通过具体案例展示了如何统计用户行为、分析订单数据,并提供了性能优化的实用技巧。掌握这些方法,能够显著提升开发效率,同时为复杂业务场景提供坚实的技术支持。

建议读者在实际项目中多加练习,尝试结合 annotate()aggregate() 和条件表达式,探索更灵活的查询方式。随着对 Django ORM 的深入理解,开发者将能更自如地应对各种数据管理挑战。

最新发布