2009-09-08 11 views
14

Sto creando un database di registrazione degli alimenti in Django e ho un problema relativo alle query.Come ordinare per Conteggio annotato() in un modello correlato in Django

Ho impostato i miei modelli per includere (tra le altre cose) un modello alimentare collegato al modello Utente tramite un campo "consumatore" del campo M2M tramite il modello Consumo. Il modello alimentare descrive i piatti di cibo e il modello di consumo descrive il consumo di cibo di un utente (data, quantità, ecc.).

class Food(models.Model): 
    food_name = models.CharField(max_length=30) 
    consumer = models.ManyToManyField("User", through=Consumption) 

class Consumption(models.Model): 
    food = models.ForeignKey("Food") 
    user = models.ForeignKey("User") 

Voglio creare una query che restituisce tutti gli oggetti cibo ordinato dal numero di volte in cui oggetto cibo compare nella Tavola dei consumi per l'utente (il numero di volte in cui l'utente ha consumato il cibo).

sto cercando qualcosa nella linea di:

Food.objects.all().annotate(consumption_times = Count(consumer)).order_by('consumption_times')` 

Ma questo sarà ovviamente contare tutti gli oggetti di consumo relativi all'oggetto alimentari, non solo quelli associati con l'utente. Devo cambiare i miei modelli o mi manca qualcosa di ovvio nelle domande?

Questa è un'operazione time-critical abbastanza (tra le altre cose, è utilizzato per riempire un campo di completamento automatico nel frontend) e la tabella alimentare ha un paio di migliaia di voci, quindi preferisco fare l'ordinamento nel database fine, piuttosto che fare il metodo della forza bruta e iterare i risultati facendo:

Consumption.objects.filter(food=food, user=user).count() 

e quindi utilizzando python sorta di ordinarli. Non credo che il metodo si ridurrebbe molto bene con l'aumentare della base di utenti e voglio progettare il database come prova futura che posso sin dall'inizio.

Qualche idea?

+0

Possibile duplicato di [Ordine dal conte di un campo ForeignKey?] (Http://stackoverflow.com/questions/2501149/order-by-count-of-a-foreignkey-field) –

risposta

21

Forse qualcosa del genere?

Food.objects.filter(consumer__user=user)\ 
      .annotate(consumption_times=Count('consumer'))\ 
      .order_by('consumption_times') 
+0

Ma questo sarebbe restituire solo gli oggetti alimentari che sono stati consumati in un determinato momento, non è vero? Voglio restituire tutti gli oggetti alimentari, ma in ordine di consumo più spesso prima. Se filtro per utente, non otterrò il cibo che non è stato ancora consumato. Un'idea potrebbe essere quella di fare due domande, la prima come quella che hai suggerito di prendere tutti gli elementi alimentari consumati almeno una volta e poi qualcosa sulla falsariga di Food.objects.exclude (consumer__user = utente) e compilare la lista con quelli . Funzionerebbe? –

+0

Sì, 2 domande sarebbero come lo farei. – SmileyChris

19

Sto riscontrando un problema molto simile. Fondamentalmente, io so che la query SQL che si desidera è:

SELECT food.*, COUNT(IF(consumption.user_id=123,TRUE,NULL)) AS consumption_times 
     FROM food LEFT JOIN consumption ON (food.id=consumption.food_id) 
     ORDER BY consumption_times; 

Quello che mi auguro è che si potrebbe mescolare funzioni di aggregazione e di espressione F, annotare le espressioni F senza una funzione di aggregazione, hanno un insieme più ricco di operazioni/funzioni per Le espressioni F e hanno campi virtuali che sono fondamentalmente un'annotazione di espressione F automatica. In modo che si potrebbe fare:

Food.objects.annotate(consumption_times=Count(If(F('consumer')==user,True,None)))\ 
      .order_by('consumtion_times') 

Inoltre, solo essere in grado più facilmente in grado di aggiungere le proprie funzioni di aggregazione complesse sarebbe bello, ma nel frattempo, ecco un hack che aggiunge una funzione di aggregazione per fare questo.

from django.db.models import aggregates,sql 
class CountIf(sql.aggregates.Count): 
    sql_template = '%(function)s(IF(%(field)s=%(equals)s,TRUE,NULL))' 
sql.aggregates.CountIf = CountIf 

consumption_times = aggregates.Count('consumer',equals=user.id) 
consumption_times.name = 'CountIf' 
rows = Food.objects.annotate(consumption_times=consumption_times)\ 
        .order_by('consumption_times') 
+0

Questo è fantastico !!! Grazie amico hai salvato la mia giornata!Cercherò di renderlo un po 'più bello, ma dovresti sicuramente inserirlo nel trac di django. –

Problemi correlati