You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Django中结合GROUP_CONCAT与点赞统计注解处理文章分类的问题

解决Django中同时统计点赞点踩与聚合多对多分类的问题

首先,你的点赞/点踩统计思路是完全可行的,但直接在现有QuerySet上添加分类聚合会遇到一个常见的坑:多对多关联会让QuerySet生成重复行,进而导致Sum统计的点赞数被重复计算。所以我们需要先通过子查询(Subquery)独立统计每篇文章的点赞点踩数,再聚合分类信息,这样就能避免计数错误。

下面分数据库类型给出完整实现方案:

1. 先处理点赞点踩的统计(避免重复计数)

我们用Subquery和OuterRef单独统计每篇文章的点赞、点踩数量,这样不会受多对多分类关联的影响:

from django.db.models import Subquery, OuterRef, IntegerField, Case, When, Sum

# 子查询:统计单篇文章的点赞数
upvotes_subquery = (
    models.Like.objects.filter(article=OuterRef('pk'), like_state=1)
    .values('article')
    .annotate(count=Sum(Case(When(like_state=1, then=1), default=0, output_field=IntegerField())))
    .values('count')
)

# 子查询:统计单篇文章的点踩数
downvotes_subquery = (
    models.Like.objects.filter(article=OuterRef('pk'), like_state=-1)
    .values('article')
    .annotate(count=Sum(Case(When(like_state=-1, then=1), default=0, output_field=IntegerField())))
    .values('count')
)

2. 添加多对多分类的聚合

根据你使用的数据库类型,选择对应的聚合方式:

情况一:使用PostgreSQL

PostgreSQL原生支持StringAgg函数,可以直接用来拼接分类名称:

from django.contrib.postgres.aggregates import StringAgg

queryset = queryset.annotate(
    upvotes_count=Subquery(upvotes_subquery, output_field=IntegerField()),
    downvotes_count=Subquery(downvotes_subquery, output_field=IntegerField()),
    # 聚合分类名称,用逗号分隔,去重避免重复分类
    category_names=StringAgg('categories__name', delimiter=', ', distinct=True)
)

情况二:使用MySQL

MySQL需要用Func封装原生的GROUP_CONCAT函数:

from django.db.models import Func

class GroupConcat(Func):
    function = 'GROUP_CONCAT'
    template = "%(function)s(%(expressions)s SEPARATOR ', ')"

queryset = queryset.annotate(
    upvotes_count=Subquery(upvotes_subquery, output_field=IntegerField()),
    downvotes_count=Subquery(downvotes_subquery, output_field=IntegerField()),
    # 聚合分类名称,用逗号分隔,去重避免重复分类
    category_names=GroupConcat('categories__name', distinct=True)
)

3. 完整代码示例(以PostgreSQL为例)

from django.db.models import Subquery, OuterRef, IntegerField, Case, When, Sum
from django.contrib.postgres.aggregates import StringAgg

# 子查询定义
upvotes_subquery = (
    models.Like.objects.filter(article=OuterRef('pk'), like_state=1)
    .values('article')
    .annotate(count=Sum(Case(When(like_state=1, then=1), default=0, output_field=IntegerField())))
    .values('count')
)

downvotes_subquery = (
    models.Like.objects.filter(article=OuterRef('pk'), like_state=-1)
    .values('article')
    .annotate(count=Sum(Case(When(like_state=-1, then=1), default=0, output_field=IntegerField())))
    .values('count')
)

# 最终QuerySet
queryset = queryset.annotate(
    upvotes_count=Subquery(upvotes_subquery, output_field=IntegerField()),
    downvotes_count=Subquery(downvotes_subquery, output_field=IntegerField()),
    category_names=StringAgg('categories__name', delimiter=', ', distinct=True)
)

关键说明

  • 使用Subquery的原因:多对多关联会让同一篇文章对应多条分类记录,直接用Sum会重复统计点赞数。子查询是针对单篇文章独立统计,彻底避免了这个问题。
  • distinct=True:确保同一个分类不会被重复拼接(比如文章和分类的关联如果存在重复记录的话)。
  • 如果你的分类显示字段不是name,替换成你实际使用的字段名即可。

内容的提问来源于stack exchange,提问作者Alexey Kislitsin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:44:38