2013-05-30 12 views
6

Ho un'app django che sta registrando. Il mio modello è simile al seguente:Conteggio aggregato Django con numero di record al giorno

class MessageLog(models.Model): 
    logtime = models.DateTimeField(auto_now_add=True) 
    user = models.CharField(max_length=50) 
    message = models.CharField(max_length=512) 

Che voglio fare è ottenere il numero medio di messaggi registrati per ogni giorno della settimana in modo che posso vedere quali giorni sono i più attivi. Sono riuscito a scrivere una query che tira il numero totale di messaggi al giorno, che è:

for i in range(1, 8): 
    MessageLog.objects.filter(logtime__week_day=i).count() 

ma sto avendo problemi a calcolare la media in una query. Quello che ho adesso è:

for i in range(1, 8): 
    MessageLog.objects.filter(logtime__week_day=i).annotate(num_msgs=Count('id')).aggregate(Avg('num_msgs')) 

Per qualche motivo, tuttavia, restituisce 1.0 per ogni giorno. Ho guardato lo SQL si sta generando ed è:

SELECT AVG(num_msgs) FROM (
SELECT 
`myapp_messagelog`.`id` AS `id`, `myapp_messagelog`.`logtime` AS `logtime`, 
`myapp_messagelog`.`user` AS `user`, `myapp_messagelog`.`message` AS `message`, 
COUNT(`myapp_messagelog`.`id`) AS `num_msgs` 
FROM `myapp_messagelog` 
WHERE DAYOFWEEK(`myapp_messagelog`.`logtime`) = 1 
GROUP BY `myapp_messagelog`.`id` ORDER BY NULL 
) subquery 

Credo che il problema potrebbe essere provenienti da GROUP BY id ma non sono davvero sicuro. Qualcuno ha qualche idea o suggerimento? Grazie in anticipo!

risposta

9

Il motivo per cui la query elencata fornisce sempre 1 è perché non si sta raggruppando per data. Fondamentalmente, hai chiesto al database di prendere le righe MessageLog che cadono in un determinato giorno della settimana. Per ciascuna di queste righe, conta quanti id ha (sempre 1). Poi prendere la media di tutti i conteggi, che naturalmente è anche 1.

Normalmente, si avrebbe bisogno di utilizzare una clausola values per raggruppare i MessageLog righe prima del Vostro annotate e aggregate parti. Tuttavia, poiché il tuo campo logtime è un datetime anziché una data, non sono sicuro che tu possa esprimerlo direttamente con l'ORM di Django. Puoi sicuramente farlo con una clausola extra, come mostrato here. Oppure, se lo ritenessi, potresti dichiarare una vista nel tuo SQL con la maggior parte dei calcoli matematici di aggregazione e media che ti sono piaciuti e dichiarare un modello non gestito, quindi usa normalmente l'ORM.

Quindi un campo extra funziona per ottenere il numero totale di record per giorno effettivo, ma non gestisce l'aggregazione della media dell'annotazione calcolata. Penso che questo possa essere sufficientemente astratto dal modello che si dovrebbe usare una query SQL grezza, o almeno non riesco a trovare nulla che lo faccia funzionare in una sola chiamata.

Detto questo, sai già come è possibile ottenere il numero totale di record per giorno della settimana in una semplice query, come mostrato nella domanda.

E questa query vi dirà quanti distinti registri aggiornati ci sono in un determinato giorno della settimana:

MessageLog.objects.filter(logtime__week_day=i).dates('logtime', day').count() 

Così si potrebbe fare la matematica della media in Python, invece, che potrebbe essere più semplice che cercare ottenere il giusto SQL .

In alternativa, questa query ti porterà il numero grezzo di messaggi per tutti i giorni feriali in una query piuttosto che un ciclo for:

MessageLog.objects.extra({'weekday': "dayofweek(logtime)"}).values('weekday').annotate(Count('id')) 

Ma io non sono stati in grado di ottenere un bel query per darvi il conteggio delle date distinte per ogni giorno della settimana annotato per quello - le date dei querysets perdono la capacità di gestire le chiamate annotate e l'annotazione su un valore extra sembra non funzionare neanche.

Questo è stato sorprendentemente difficile, dato che non è così difficile un'espressione SQL.

+0

Grazie per la spiegazione! Questo ha molto senso. Ho provato a utilizzare la clausola extra, tuttavia ora sto ottenendo un errore SQL. Ho modificato la mia richiesta di essere: MessageLog.objects.filter (logtime__week_day = i) .extra ({'date_logged': "date (logtime)"}). Values ​​('date_logged') .annotate (num_msgs = Count ('id')) .aggregate (Avg ('num_msgs')) E l'errore che ricevo è "Hai un errore nella sintassi SQL, controlla il manuale che corrisponde alla versione del tuo server MySQL per la sintassi corretta a usa vicino a 'FROM (SELECT (data (logtime)) AS date_logged', COUNT ('myapp_messagelog' .id' alla riga 1") – bb89

+0

Sì, non funziona, vero? La clausola aggregata la butta via - it funziona fino all'annotazione. Puoi ottenere un conteggio del numero totale di messaggi per coppia di giorni della settimana in modi diversi, ma non riesco a trovare un modo per fare in modo che l'ORM di Django faccia questo in un'unica chiamata. quello che ho inventato h nella mia risposta. –

+0

Alla fine ho creato una vista (effettivamente necessaria 2 per mysql) e quindi un modello non gestito come suggerito e ha funzionato perfettamente. Aggiungerò un altro post che spiega cosa ho fatto in seguito per quelli che potrebbero essere interessati. Grazie ancora per tutto il vostro aiuto! – bb89

2

Faccio qualcosa di simile con un campo datetime, ma l'annotazione su valori extra funziona per me. Ho un modello Record con un campo datetime "created_at" e un campo "my_value" per cui voglio ottenere la media.

from django.db.models import Avg 

qs = Record.objects.extra({'created_day':"date(created_at)"}).\ 
    values('created_day').\ 
    annotate(count=Avg('my_value) 

Quanto sopra raggrupperà per il giorno del valore datetime nel campo "created_at".