Come fare SELECT COUNT (*) GROUP BY e ORDER BY in Django?


95

Sto usando un modello di transazione per tenere traccia di tutti gli eventi che attraversano il sistema

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() 
    ......

come ottengo i primi 5 attori nel mio sistema?

In sql sarà fondamentalmente

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

Risposte:


176

Secondo la documentazione, dovresti usare:

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

valori (): specifica quali colonne verranno utilizzate per "raggruppare per"

Documenti Django:

"Quando una clausola values ​​() viene utilizzata per vincolare le colonne restituite nel set di risultati, il metodo di valutazione delle annotazioni è leggermente diverso. Invece di restituire un risultato annotato per ciascun risultato nel QuerySet originale, i risultati originali vengono raggruppati in base alle combinazioni univoche dei campi specificati nella clausola values ​​() "

annotate (): specifica un'operazione sui valori raggruppati

Documenti Django:

Il secondo modo per generare valori di riepilogo consiste nel generare un riepilogo indipendente per ogni oggetto in un QuerySet. Ad esempio, se stai recuperando un elenco di libri, potresti voler sapere quanti autori hanno contribuito a ciascun libro. Ogni libro ha una relazione molti-a-molti con l'Autore; vogliamo riepilogare questa relazione per ogni libro nel QuerySet.

I riepiloghi per oggetto possono essere generati utilizzando la clausola annotate (). Quando viene specificata una clausola annotate (), ogni oggetto nel QuerySet verrà annotato con i valori specificati.

La clausola order by è autoesplicativa.

Riassumendo: raggruppate per, generando un set di query di autori, aggiungete l'annotazione (questo aggiungerà un campo extra ai valori restituiti) e infine li ordinate in base a questo valore

Fare riferimento a https://docs.djangoproject.com/en/dev/topics/db/aggregation/ per ulteriori informazioni

Da notare: se si utilizza Count, il valore passato a Count non influisce sull'aggregazione, ma solo sul nome dato al valore finale. L'aggregatore raggruppa per combinazioni univoche dei valori (come menzionato sopra), non per il valore passato a Count. Le seguenti query sono le stesse:

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

Per me ha funzionato come Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total'), non dimenticare di importare Count da django.db.models. Grazie
Ivancho

3
Buono a notare: se si utilizzano Count(e forse altri aggregatori), il valore passato a Countnon effettua l'aggregazione, ma solo il nome dato al valore finale. L'aggregatore raggruppa per combinazioni univoche di values(come menzionato sopra), non per il valore passato a Count.
kronosapiens

Puoi anche usarlo per i set di query dei risultati di ricerca postgres per ottenere sfaccettature!
yekta

2
@kronosapiens Lo influenza, almeno oggigiorno (sto usando Django 2.1.4). Nell'esempio, totalviene fornito il nome e il conteggio utilizzato in sql è COUNT('actor')che in questo caso non ha importanza, ma se ad es values('x', 'y').annotate(count=Count('x')). Otterrai COUNT(x), non COUNT(*)o COUNT(x, y)lo provi semplicemente in./manage.py shell
timdiels

33

Proprio come @Alvaro ha risposto all'equivalente diretto di Django per la GROUP BYdichiarazione:

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

è attraverso l'uso di values()e annotate()metodi come segue:

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

Tuttavia un'ultima cosa deve essere sottolineata:

Se il modello ha un ordinamento predefinito definito in class Meta, la .order_by()clausola è obbligatoria per risultati corretti. Non puoi saltarlo nemmeno quando non è previsto alcun ordine.

Inoltre, per un codice di alta qualità si consiglia di inserire sempre una .order_by()clausola dopo annotate(), anche quando non c'è class Meta: ordering. Tale approccio renderà la dichiarazione a prova di futuro: funzionerà esattamente come previsto, indipendentemente da eventuali modifiche future a class Meta: ordering.


Lascia che ti fornisca un esempio. Se il modello avesse:

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']

Allora tale approccio NON FUNZIONERÀ:

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

Questo perché Django si esibisce in più GROUP BYsu ogni campo inclass Meta: ordering

Se vuoi stampare la query:

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

Sarà chiaro che l'aggregazione NON funzionerà come previsto e quindi la .order_by()clausola deve essere utilizzata per cancellare questo comportamento e ottenere risultati di aggregazione appropriati.

Vedi: Interazione con l'ordinamento predefinito o order_by () nella documentazione ufficiale di Django.


3
.order_by()mi ha salvato da orderingMeta.
Babken Vardanyan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.