Django: заказать набор запросов по сумме аннотированных полей?

Я создаю сайт 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, если это имеет значение.

Приветствуются любые идеи!


person davidscolgan    schedule 03.04.2013    source источник
comment
docs.djangoproject.com/en/dev/topics/db / aggregation / Разве этого недостаточно?   -  person karthikr    schedule 04.04.2013
comment
Агрегат, похоже, не поможет в этом случае, потому что мне нужно вернуть набор запросов объектов обсуждения, а не только количество каждого фактора оценки.   -  person davidscolgan    schedule 04.04.2013


Ответы (2)


Я не думаю, что это возможно в одном 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;
person Tom Nurkkala    schedule 05.04.2013
comment
Это легко запустить из Django, используя необработанный SQL-запрос: Discussion.objects.raw('''SELECT ... etc ...''') - person Gareth Rees; 05.04.2013

На самом деле есть способ использовать дополнительную аннотацию с F-выражения:

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')
person Sébastien Deprez    schedule 31.03.2016
comment
Технически они должны быть внутри ExpressionWrapper на случай, если вам понадобится какая-либо форма литья и т. Д. - person Pythonista; 26.12.2019