Django:通过注释字段的总和订购查询集?

Posted

技术标签:

【中文标题】Django:通过注释字段的总和订购查询集?【英文标题】:Django: order a queryset by the sum of annotated fields? 【发布时间】:2013-03-25 20:36:05 【问题描述】:

我正在构建一个 Django 站点以供讨论。用户可以参与讨论,也可以对讨论和讨论中的消息投赞成票。一个简化的数据模型如下:

class Discussion:
    name = models.CharField(max_length=255)

class Message:
    owner = models.ForeignKey(User, related_name='messages')
    body = models.TextField()
    discussion = models.ForeignKey(Discussion, related_name='messages')

class MessageApprovalVote:
    owner = models.ForeignKey(User, related_name='message_approval_votes')
    message = models.ForeignKey(Message, related_name='approval_votes')

class DiscussionApprovalVote:
    owner = models.ForeignKey(User, related_name='discussion_approval_votes')
    discussion = models.ForeignKey(Discussion, related_name='approval_votes')

我想选择前 20 个“最活跃”的讨论,这意味着按该讨论的消息数、消息批准投票总数和讨论批准投票数的总和排序,或者(在伪代码中) :

# Doesn't work
Discussion.objects.
    order_by(Count('messages') + 
             Count('approval_votes') + 
             Count('messages__approval_votes'))

使用注释,我可以计算三个评分因素的总和:

scores = Discussion.objects.annotate(
    total_messages=Count('messages', distinct=True),
    total_discussion_approval_votes=Count('approval_votes', distinct=True),
    total_message_approval_votes=Count('messages__approval_votes', distinct=True))

当我找到extra 方法时,我以为我正在做某事:

total_scores = scores.extra(
    select=
        'score_total': 'total_messages + total_discussion_approval_votes + total_message_approval_votes'
    
)

然后就能做到:

final_answer = total_scores.order_by('-score_total')[:20]

但是extra 调用给出了DatabaseError

DatabaseError: column "total_messages" does not exist
LINE 1: SELECT (total_votes + total_messages + total_persuasions) AS...

因此我被挫败了。 extra 方法可以不引用annotated 字段吗?除了使用原始 sql 查询之外,还有其他方法可以做我想做的事情吗?如果有什么不同,我会使用 Postgres。

任何见解都将不胜感激!

【问题讨论】:

docs.djangoproject.com/en/dev/topics/db/aggregation/… 这还不够有用吗? 在这种情况下,聚合似乎没有帮助,因为我需要返回讨论对象的查询集,而不仅仅是每个评分因素的计数。 【参考方案1】:

我认为这在单个*** SQL 查询中是不可能的。 score_total 值取决于三个汇总结果,但您要求同时计算它们。

在直接 SQL 中,您可以使用子查询来执行此操作,但我不确定如何将其注入 Django。在使用您的模型设置一个简单的 Django 应用程序后,以下查询似乎对 SQLite 数据库起到了作用:

SELECT  id, name,
    total_messages, total_discussion_approval_votes, total_message_approval_votes,
    (total_messages +
     total_discussion_approval_votes +
     total_message_approval_votes) as score_total
FROM
   (SELECT
    discussion.id,
    discussion.name,
    COUNT(DISTINCT discussionapprovalvote.id) AS total_discussion_approval_votes,
    COUNT(DISTINCT messageapprovalvote.id) AS total_message_approval_votes,
    COUNT(DISTINCT message.id) AS total_messages
    FROM discussion
    LEFT OUTER JOIN discussionapprovalvote
         ON (discussion.id = discussionapprovalvote.discussion_id)
    LEFT OUTER JOIN message
         ON (discussion.id = message.discussion_id)
    LEFT OUTER JOIN messageapprovalvote
         ON (message.id = messageapprovalvote.message_id)
    GROUP BY discussion.id, discussion.name)

ORDER BY score_total DESC
LIMIT 20;

【讨论】:

使用 raw SQL query: Discussion.objects.raw('''SELECT ... etc ...''') 从 Django 运行它很容易【参考方案2】:

其实有一种方法是使用 F expressions 的额外注解:

Discussion.objects.annotate(
    total_messages=Count('messages', distinct=True),
    total_discussion_approval_votes=Count('approval_votes', distinct=True),
    total_message_approval_votes=Count('messages__approval_votes', distinct=True)),
    total_score=F('total_messages') + F('total_discussion_approval_votes') + F('total_message_approval_votes')
).order_by('total_score')

【讨论】:

从技术上讲,这些应该在ExpressionWrapper 内,以防您需要任何形式的投射等

以上是关于Django:通过注释字段的总和订购查询集?的主要内容,如果未能解决你的问题,请参考以下文章

带有字段比较结果的django注释查询集

Django通过谓词和计数结果注释查询集

Django - 查询:使用相关模型字段注释查询集

如何通过函数的结果(总和)订购选择?

如何通过查询集(逐个对象)获取每个对象的总和

带有注释的Django查询集,为啥将GROUP BY应用于所有字段?