web-dev-qa-db-fra.com

Comment sélectionner SELECT COUNT (*) GROUP BY et ORDER BY dans Django?

J'utilise un modèle de transaction pour garder une trace de tous les événements qui traversent le système

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

comment puis-je obtenir les 5 meilleurs acteurs de mon système?

En sql ce sera essentiellement

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

Selon la documentation, vous devriez utiliser:

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

values ​​(): spécifie quelles colonnes seront utilisées pour "grouper par"

Django docs:

"Lorsqu'une clause values ​​() est utilisée pour contraindre les colonnes renvoyées dans le jeu de résultats, la méthode d'évaluation des annotations est légèrement différente. Au lieu de renvoyer un résultat annoté pour chaque résultat dans le QuerySet d'origine, les résultats d'origine sont regroupés aux combinaisons uniques des champs spécifiés dans la clause values ​​() "

annotate (): spécifie une opération sur les valeurs groupées

Django docs:

La deuxième façon de générer des valeurs de résumé consiste à générer un résumé indépendant pour chaque objet d'un ensemble de requêtes. Par exemple, si vous récupérez une liste de livres, vous voudrez peut-être savoir combien d'auteurs ont contribué à chaque livre. Chaque livre entretient de nombreuses relations avec l'auteur. nous voulons résumer cette relation pour chaque livre du QuerySet.

Les résumés par objet peuvent être générés à l'aide de la clause annotate (). Lorsqu'une clause annotate () est spécifiée, chaque objet du QuerySet est annoté avec les valeurs spécifiées.

L'ordre par clause est explicite.

Pour résumer: vous groupez par, générant un ensemble de requêtes d'auteurs, ajoutez l'annotation (cela ajoutera un champ supplémentaire aux valeurs retournées) et finalement, vous les classerez par cette valeur.

Reportez-vous à https://docs.djangoproject.com/en/dev/topics/db/aggregation/ pour plus d'informations.

133
Alvaro

Tout comme @Alvaro a répondu à l'équivalent direct de Django pour l'instruction GROUP BY:

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

est à travers l'utilisation de values() et annotate() méthodes comme suit:

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

Cependant, une dernière chose doit être signalée:

Si le modèle a un ordre par défaut défini dans class Meta, La clause .order_by() est obligatoire pour des résultats corrects. Vous ne peut tout simplement pas le sauter, même si aucune commande n'est prévue.

De plus, pour un code de haute qualité, il est conseillé de toujours mettre une clause .order_by() après annotate(), même en l'absence de class Meta: ordering. Une telle approche rendra la déclaration pérenne: elle fonctionnera comme prévu, sans tenir compte des modifications futures apportées à class Meta: ordering.


Laissez-moi vous donner un exemple. Si le modèle avait:

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

Alors une telle approche ne fonctionnerait pas:

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

C'est parce que Django exécute des GROUP BY Supplémentaires sur chaque champ de class Meta: ordering

Si vous souhaitez imprimer la requête:

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

Il est clair que l’agrégation NE fonctionnerait PAS comme prévu et, par conséquent, la clause .order_by() doit être utilisée pour éliminer ce comportement et obtenir les résultats appropriés de l’agrégation.

Voir: Interaction avec la commande par défaut ou order_by () dans la documentation officielle Django documentation.

25
Krzysiek