Wie wähle ich COUNT (*) GROUP BY und ORDER BY in Django aus?

92

Ich verwende ein Transaktionsmodell, um alle Ereignisse im System zu verfolgen

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField() 
    ......

Wie bekomme ich die Top 5 Schauspieler in meinem System?

In SQL wird es im Grunde sein

SELECT actor, COUNT(*) as total 
FROM Transaction 
GROUP BY actor 
ORDER BY total DESC
totoromeow
quelle

Antworten:

171

Gemäß der Dokumentation sollten Sie verwenden:

from django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')

values ​​(): Gibt an, welche Spalten zum "Gruppieren nach" verwendet werden sollen.

Django docs:

"Wenn eine values ​​() -Klausel verwendet wird, um die in der Ergebnismenge zurückgegebenen Spalten einzuschränken, unterscheidet sich die Methode zum Auswerten von Anmerkungen geringfügig. Anstatt für jedes Ergebnis im ursprünglichen QuerySet ein kommentiertes Ergebnis zurückzugeben, werden die ursprünglichen Ergebnisse entsprechend gruppiert zu den eindeutigen Kombinationen der in der values ​​() -Klausel angegebenen Felder "

annotate (): Gibt eine Operation über die gruppierten Werte an

Django docs:

Die zweite Möglichkeit, Zusammenfassungswerte zu generieren, besteht darin, für jedes Objekt in einem QuerySet eine unabhängige Zusammenfassung zu generieren. Wenn Sie beispielsweise eine Liste von Büchern abrufen, möchten Sie möglicherweise wissen, wie viele Autoren zu jedem Buch beigetragen haben. Jedes Buch hat eine Viele-zu-Viele-Beziehung zum Autor. Wir möchten diese Beziehung für jedes Buch im QuerySet zusammenfassen.

Zusammenfassungen pro Objekt können mithilfe der annotate () -Klausel generiert werden. Wenn eine annotate () -Klausel angegeben wird, wird jedes Objekt im QuerySet mit den angegebenen Werten versehen.

Die Reihenfolge nach Klausel ist selbsterklärend.

Zusammenfassend: Sie gruppieren nach, generieren eine Abfragemenge von Autoren, fügen die Anmerkung hinzu (dies fügt den zurückgegebenen Werten ein zusätzliches Feld hinzu) und ordnen sie schließlich nach diesem Wert

Siehe https://docs.djangoproject.com/de/dev/topics/db/aggregation/Weitere

Gut zu beachten: Wenn Sie Count verwenden, wirkt sich der an Count übergebene Wert nicht auf die Aggregation aus, sondern nur auf den Namen des endgültigen Werts. Der Aggregator gruppiert nach eindeutigen Kombinationen der Werte (wie oben erwähnt) und nicht nach dem an Count übergebenen Wert. Folgende Abfragen sind gleich:

Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')
Transaction.objects.all().values('actor').annotate(total=Count('id')).order_by('total')
Alvaro
quelle
Bei mir hat es funktioniert Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total'), vergessen Sie nicht, Count aus django.db.models zu importieren. Vielen Dank
Ivancho
3
Gut zu beachten: Wenn Sie Count(und möglicherweise andere Aggregatoren) verwenden, Countwirkt sich der übergebene Wert nicht auf die Aggregation aus, sondern nur auf den Namen des endgültigen Werts. Der Aggregator gruppiert nach eindeutigen Kombinationen der values(wie oben erwähnt), nicht nach dem an übergebenen Wert Count.
kronosapiens
Sie können dies sogar für Postgres-Suchergebnis-Abfragesätze verwenden, um Facettierung zu erhalten!
Yekta
2
@kronosapiens Zumindest heutzutage ist es davon betroffen (ich verwende Django 2.1.4). Im Beispieltotal ist der Name angegeben und die Anzahl in SQL verwendet, COUNT('actor')was in diesem Fall keine Rolle spielt, aber wenn Sie zB values('x', 'y').annotate(count=Count('x'))erhalten COUNT(x), nicht COUNT(*)oder COUNT(x, y), versuchen Sie es einfach in./manage.py shell
timdiels
33

Genau wie @Alvaro das direkte Äquivalent des Django zur GROUP BYAussage beantwortet hat :

SELECT actor, COUNT(*) AS total 
FROM Transaction 
GROUP BY actor

ist durch die Verwendung von values()und annotate()Methoden wie folgt:

Transaction.objects.values('actor').annotate(total=Count('actor')).order_by()

Es muss jedoch noch eines hervorgehoben werden:

Wenn für das Modell eine Standardreihenfolge definiert ist class Meta, ist die .order_by()Klausel für ordnungsgemäße Ergebnisse obligatorisch. Sie können es einfach nicht überspringen, auch wenn keine Bestellung beabsichtigt ist.

Für einen qualitativ hochwertigen Code wird außerdem empfohlen, immer eine .order_by()Klausel nachzusetzen annotate(), auch wenn keine vorhanden ist class Meta: ordering. Ein solcher Ansatz macht die Aussage zukunftssicher: Sie funktioniert wie beabsichtigt, unabhängig von zukünftigen Änderungen an class Meta: ordering.


Lassen Sie mich Ihnen ein Beispiel geben. Wenn das Modell hätte:

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField()

    class Meta:
        ordering = ['id']

Dann würde ein solcher Ansatz NICHT funktionieren:

Transaction.objects.values('actor').annotate(total=Count('actor'))

Das liegt daran, dass Django GROUP BYauf jedem Feld in zusätzlich zusätzliche Leistungen erbringtclass Meta: ordering

Wenn Sie die Abfrage drucken würden:

>>> print Transaction.objects.values('actor').annotate(total=Count('actor')).query
  SELECT "Transaction"."actor_id", COUNT("Transaction"."actor_id") AS "total"
  FROM "Transaction"
  GROUP BY "Transaction"."actor_id", "Transaction"."id"

Es ist klar, dass die Aggregation NICHT wie beabsichtigt funktionieren würde, und daher .order_by()muss die Klausel verwendet werden, um dieses Verhalten zu löschen und korrekte Aggregationsergebnisse zu erhalten.

Siehe: Interaktion mit Standardbestellung oder order_by () in der offiziellen Django-Dokumentation.

Krzysiek
quelle
3
.order_by()hat mich orderingin Meta gerettet .
Babken Vardanyan