Django:通过注释字段的总和来命令查询集?

时间:2013-04-03 23:13:43

标签: python sql django django-orm

我正在建立一个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方法可以不引用annotate字段吗?有没有其他方法可以做我正在尝试做的事情,没有使用原始的SQL查询?如果有所作为,我正在使用Postgres。

非常感谢任何见解!

2 个答案:

答案 0 :(得分:4)

我不认为在单个顶级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;

答案 1 :(得分:0)

实际上,通过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')