count() 适合简单计数,但勿在循环中调用;aggregate() 用于全表聚合统计且不可链式调用;annotate() 才能为每条记录添加统计字段,需注意 distinct 避免 JOIN 重复计数。

count() 方法适合简单计数,但别在循环里调用
直接调用 QuerySet.count() 是最常用的计数方式,它生成一条 COUNT(*) SQL,不加载对象到内存。但很多人会在 for 循环里反复调用,比如:
for user in User.objects.filter(is_active=True):
print(user.profile_set.count()) # 每次都查一次数据库!这会导致 N+1 查询问题,性能极差。正确做法是提前用 prefetch_related 或 annotate 一次性带出数量。
aggregate() 适合带条件或组合统计,但不能链式调用 filter
aggregate() 返回字典,用于单次聚合计算,比如总和、最大值、计数(带条件)。但它返回的是一个结果字典,不是 QuerySet,所以不能接 filter()、order_by() 等方法。
- 错误写法:
User.objects.aggregate(count=Count('id')).filter(is_active=True)→ 报错AttributeError: 'dict' object has no attribute 'filter' - 正确写法:条件必须写在
aggregate()前的QuerySet上,如User.objects.filter(is_active=True).aggregate(count=Count('id')) - 想同时算多个指标?传多个
Count、Avg等,比如.aggregate(active=Count('id', filter=Q(is_active=True)), total=Count('id'))
Count('field') 和 Count('*') 在 NULL 上行为不同
Count('field') 默认忽略 NULL 值,而 Count('*') 或 Count(1) 统计所有行。比如统计有邮箱的用户数:User.objects.aggregate(email_count=Count('email')) 不会把 email=None 的行算进去;但如果你想要所有用户数,得用 Count('id') 或显式写 Count('*')(Django 3.2+ 支持)。另外,Count('field', distinct=True) 可去重,但注意它只对关联字段或外键有意义,对普通字段去重通常不是你想要的效果。
annotate() 才是“给每条记录加个 count 字段”的正解
如果目标是“查出每个用户有多少篇文章”,必须用 annotate(),不是 aggregate()。
-
aggregate()→ 返回一个字典,整个 QuerySet 就一个总数 -
annotate()→ 返回 QuerySet,每条记录多一个字段,比如User.objects.annotate(post_count=Count('post')) - 注意关联名大小写:字段名是模型中定义的
related_name,不是表名;没设的话默认是小写模型名_set - 如果用了
select_related或prefetch_related,再加annotate要小心重复计数(JOIN 导致行膨胀),此时应加distinct=True,如Count('post', distinct=True)
真正容易卡住的地方,往往不是语法,而是搞混了 aggregate 和 annotate 的作用域——前者是“全表汇总”,后者是“逐行打标”。一旦在列表页里错用 aggregate,就只能拿到一个数字,而不是每条数据都带统计值。










