2017-04-26 2 views
1

Verwenden von Django Rest Framework 3.x und Django 1.1.10. Ich habe ein Modell, das Benutzer darstellt. Wenn ich alle Benutzer aufrufe, indem ich auf den Endpunkt /users/ im DRF zugreife, muss die Liste einige weitere Daten enthalten, die sich auf Benutzer über ein anderes Modell, den Besitzer, beziehen. Jeder Artikel hat einen Besitzer und Besitzer haben Benutzer.Komplexe Aggregation in Django

Ich machte eine zusätzliche Eigenschaft auf das Benutzermodell und es gibt nur ein JSON-Array der Daten zurück. Das kann ich nicht ändern, denn es ist eine Anforderung an das Frontend. Ich muss eine Gesamtzahl von Elementen zurückgeben, die zu jedem Benutzer gehören, und es gibt drei verschiedene Zählungen, um die Daten zu erhalten.

Ich muss mehrere count() von Elementen auf dem gleichen Modell, aber mit unterschiedlichen Bedingungen erhalten.

diese separat zu tun, ist einfach, zwei sind trivial und die letzte ist komplizierter:

Item.objects.filter(owner__user=self).count() 
Item.objects.filter(owner__user=self, published=True).count() 
Item.objects.filter(Q(history__action__name='argle') | Q(history__action__name='bargle'), 
        history__since__lte=now, 
        history__until__gte=now, 
        owner__user=self).count() 

Das Problem ist, weil dies für jeden Benutzer ausgeführt wird, und es gibt viele von ihnen. Letztendlich generiert dies mehr als 300 DB-Abfragen und ich möchte diese auf ein Minimum reduzieren.

Bisher habe ich kam mit dieser:

Item.objects.filter(owner__user=self)\ 
      .aggregate(published=Count('published'), 
         total=Count('id')) 

Diese ersten beiden Zählungen aggregieren wird, bringt sie und nur ein SELECT werden auf der Datenbank durchgeführt werden. Gibt es eine Möglichkeit, den letzten count() Anruf in das gleiche aufzunehmen?

Ich habe viele Dinge versucht, aber es scheint unmöglich. Sollte ich einfach eine benutzerdefinierte SELECT schreiben und Item.objects.raw() verwenden?

Ich habe auch bemerkt, dass die aggregate() und die letzte count() Durchführung schneller auf meiner Entwicklungsmaschine und SQLite als auf dem Staging-Server mit Postgresql, die ein bisschen seltsam, aber es ist nicht meine größte Sorge jetzt.

Antwort

1

Da Sie die Zählungen für jedes Element in Ihrem QuerySet benötigen, sollten Sie annotate anstelle von Aggregat verwenden, das führt dann nur 1 Abfrage durch.

Der beste Weg für verwandte Objekte auf der Grundlage einer Bedingung zu zählen ist conditional aggregation

User.objects.annotate(
    total_items=Count('items'), 
    published=Sum(Case(When(items__published=True, then=1), output_field=IntegerField())), 
    foo=Sum(Case(When(
     Q(history__action__name='argle') | Q(history__action__name='bargle'), 
     history__since__lte=now, 
     history__until__gte=now, 
     then=1 
    ), output_field=IntegerField())) 
) 
+0

zu bedienen Oh, das ist brillant. Ja, jeden Benutzer mit den Artikelzählungen zu kommentieren ist der richtige Weg. Ich war zu sehr auf die Dinge selbst konzentriert. Vielen Dank! – BigWhale