2009-09-08 10 views
14

Ich baue eine Lebensmittel-Logging-Datenbank in Django und ich habe eine Abfrage bezogenen Problem.Sortieren nach annotierten Count() in einem verwandten Modell in Django

Ich habe meine Modelle so eingerichtet, dass sie unter anderem ein Food-Modell enthalten, das über das Consumption-Modell über ein M2M-Feld "Consumer" mit dem User-Modell verbunden ist. Das Food-Modell beschreibt Food-Gerichte und das Consumption-Modell beschreibt den Verzehr eines Lebensmittels (Datum, Menge, etc.).

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

Ich möchte eine Abfrage erstellen, die alle durch die Anzahl der bestellten Speisen Objekte zurückgibt, die Lebensmittel-Objekt für den Benutzer in der Verbrauchstabelle erscheint (die Anzahl der der Benutzer die Nahrung verbraucht hat).

Ich versuche, etwas in der Linie:

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

Aber das wird natürlich alle Verbrauchsobjekte zum Nahrungsmittelobjektbezogenen zählen, nicht nur die dem Benutzer zugeordnet diejenigen. Muss ich meine Modelle ändern oder fehle ich gerade etwas Offensichtliches in den Abfragen?

Dies ist eine ziemlich zeitkritische Operation (unter anderem wird es verwendet, um eine automatische Vervollständigung Feld im Frontend zu füllen) und die Nahrungsmitteltabelle hat ein paar tausend Einträge, also würde ich eher in der Datenbank die Sortierung tun Ende, anstatt die Brute-Force-Methode zu tun und die Ergebnisse iterieren tun:

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

und dann sortiert mit python, sie zu sortieren. Ich denke nicht, dass diese Methode sehr gut skalieren würde, da die Benutzerbasis zunimmt und ich die Datenbank so zukunftssicher wie möglich gestalten möchte.

Irgendwelche Ideen?

+0

Mögliche Duplikat [Sortieren nach Zählung eines ForeignKey Feld?] (Http://stackoverflow.com/questions/2501149/order-by-count-of-a-foreignkey-field) –

Antwort

21

Vielleicht so etwas?

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

Aber dies würde gib nur die Food-Objekte zurück, die irgendwann konsumiert wurden, oder? Ich möchte alle Nahrungsmittelobjekte zurückgeben, aber in der Reihenfolge der am häufigsten konsumierten zuerst. Wenn ich nach Benutzer filtern, bekomme ich nicht das Essen, das noch nicht verbraucht wurde. Eine Idee wäre vielleicht, zwei Abfragen zu machen, zuerst eine wie Sie vorgeschlagen, um alle Nahrungsmittel mindestens einmal zu konsumieren und dann etwas in der Art von Food.objects.exclude (consumer__user = user) und füllen Sie die Liste mit diesen . Funktioniert das? –

+0

Ja, 2 Anfragen wären, wie ich es machen würde. – SmileyChris

19

Ich habe ein sehr ähnliches Problem. Im Grunde weiß ich, dass die SQL-Abfrage Sie wollen, ist:

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; 

Was ich möchte, ist, dass Sie Aggregatfunktionen und F Ausdruck mischen könnten, mit Anmerkungen versehen F Ausdrücke ohne Aggregatfunktion, habe einen reicheren Satz von Operationen/Funktionen für F-Ausdrücke und virtuelle Felder, die im Grunde eine automatische F-Ausdruck-Annotation sind. Damit Sie tun könnten:

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

Auch in der Lage, mehr nur leicht in der Lage Ihre eigenen komplexen Aggregatfunktionen hinzuzufügen wäre schön, aber in der Zwischenzeit, hier ist ein Hack, der eine Aggregatfunktion zu tun dies summiert.

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

Das ist genial !!! Danke Mann, du hast mir den Tag gerettet!Ich werde versuchen, es ein bisschen schöner aussehen zu lassen, aber du solltest das definitiv in Djangos Trac ablegen. –

Verwandte Themen