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
模型,每个字段(如 title
、content
)对应模型的属性。
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 使用 Case
和 When
实现条件统计
假设要统计用户订单中,价格高于 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 的深入理解,开发者将能更自如地应对各种数据管理挑战。