2013-04-03 12 views
5

Sto costruendo un sito Django per discussioni. Gli utenti possono partecipare alle discussioni e possono anche esprimere voti di approvazione per discussioni e messaggi nelle discussioni. Un modello di dati semplificata è la seguente:Django: ordina un queryset sommando i campi annotati?

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') 

voglio selezionare i primi 20 "più attivo" discussioni, il che significa che l'ordinazione dalla somma del numero di messaggi, il numero totale di approvazione messaggio voti, e il numero di approvazione discussione vota per quella discussione, o (in pseudocodice):

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

Uso delle annotazioni, posso calcolare i totali di ciascuno dei fattori di tre gol:

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)) 

ho poi t hought ero a qualcosa quando ho trovato il metodo extra:

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

e sarebbe quindi in grado di fare:

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

ma la chiamata extra dà un DatabaseError:

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

e così sono stato sventato. Il metodo extra non fa riferimento ai campi annotate d? C'è un altro modo per fare ciò che sto cercando di fare, a meno di usare una query sql raw? Sto usando Postgres se questo fa la differenza.

Qualsiasi approfondimento sarebbe molto apprezzato!

+0

https : //docs.djopoproject.com/en/dev/topics/db/aggregation/#aggregating-annotations Non è abbastanza utile? – karthikr

+0

Un aggregato non sembra essere d'aiuto in questo caso, perché ho bisogno di recuperare un queryset degli oggetti di discussione, non solo i conteggi di ciascun fattore di punteggio. – davidscolgan

risposta

4

Non credo sia possibile in una singola query SQL di primo livello. Il valore score_total dipende dai tre risultati aggregati, ma stai chiedendo che tutti vengano calcolati allo stesso tempo.

In SQL diritta, è possibile eseguire questa operazione con una sottoquery, ma non sono sicuro di come inserirla in Django. Dopo aver impostato una semplice Django app con i tuoi modelli, la seguente query sembra fare il trucco in un database 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; 
+2

È facile eseguirlo da Django usando una [query SQL raw] (https://docs.djangoproject.com/en/dev/topics/db/sql/): 'Discussion.objects.raw ('' 'SELECT. .. ecc. '' ') ' –

0

In realtà non c'è un modo utilizzando un annotare in più con 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') 
Problemi correlati