2013-02-12 7 views
10

Devo compilare un modello con un riepilogo dell'attività dell'utente in un semplice sistema di messaggistica. Per ciascun mittente del messaggio, desidero il numero di messaggi inviati e il numero di destinatari distinti.Versione ORM di Django di SQL COUNT (DISTINCT <column>)

Ecco una versione semplificata del modello:

class Message(models.Model): 
    sender = models.ForeignKey(User, related_name='messages_from') 
    recipient = models.ForeignKey(User, related_name='messages_to') 
    timestamp = models.DateTimeField(auto_now_add=True) 

Ecco come lo farei in SQL:

SELECT sender_id, COUNT(id), COUNT(DISTINCT recipient_id) 
    FROM myapp_messages 
    GROUP BY sender_id; 

Ho letto attraverso la documentazione sull'aggregazione nelle query ORM, e sebbene annotate() sia in grado di gestire la prima colonna COUNT, non vedo un modo per ottenere il risultato COUNT (DISTINCT) (anche extra (select = {}) non ha funzionato, anche se sembra come dovrebbe). Questo può essere tradotto in una query ORG di Django o dovrei limitarmi a SQL raw?

+0

Il [ ' .distinct() '] (https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.distinct) filter potrebbe com e utile. –

+0

Se ignoriamo il COUNT (DISTINCT recipient_id) è piuttosto semplice usare le funzioni annotate e Count. Qui, il distinto() di django non aiuta. Non penso sia possibile integrare queste due cose (annotate e distinte) insieme. Devi scrivere due domande, credo. – jurgenreza

risposta

8

È infatti possibile utilizzare distinte e contare insieme, come si è visto su questa risposta: https://stackoverflow.com/a/13145407/237091

Nel tuo caso:

SELECT sender_id, COUNT(id), COUNT(DISTINCT recipient_id) 
FROM myapp_messages 
GROUP BY sender_id; 

sarebbe diventato:

Message.objects.values('sender').annotate(
    message_count=Count('sender'), 
    recipient_count=Count('recipient', distinct=True)) 
4
from django.db.models import Count 

messages = Message.objects.values('sender').annotate(message_count=Count('sender')) 

for m in messages: 
    m['recipient_count'] = len(Message.objects.filter(sender=m['sender']).\ 
           values_list('recipient', flat=True).distinct()) 
+0

Grazie! Ho ragione nel capire che questo significa che ci saranno tante query sul database quante sono i mittenti (più uno)? – nephtes

+0

@nephtes Sì. Fondamentalmente non penso che ci sia un modo per usare distinct() all'interno di Count(). Forse, stai meglio con raw sql. Penso che questo sia il modo migliore per farlo con Django, a meno che gli altri ragazzi non ne escano con un'idea migliore! – jurgenreza

+1

@jurgenreza (So che questo post è vecchio ma ...) Credo che un modo migliore sarebbe usare extra: Message.objects.filter (...). Extra ({'destinatario_count': 'COUNT (DISTINCT recipient_id) '}). I valori (' recipient_count '). Documenti extra: https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.extra – paperreduction