2017-04-26 3 views
1

Utilisation de Django Rest Framework 3.x et Django 1.1.10. J'ai un modèle qui représente les utilisateurs. Lorsque je liste tous les utilisateurs en accédant au point de terminaison /users/ dans DRF, la liste doit inclure d'autres données liées aux utilisateurs via un autre modèle, appelé Owner. Chaque article a un propriétaire et les propriétaires ont des utilisateurs.Agrégation complexe dans Django

J'ai fait une propriété supplémentaire sur le modèle User et il retourne juste un tableau JSON des données. C'est quelque chose que je ne peux pas changer, parce que c'est une exigence sur le front-end. Je dois retourner un nombre total d'éléments qui sont liés à chaque utilisateur et il y a trois comptes différents à effectuer pour obtenir les données.

J'ai besoin de plusieurs count() d'éléments sur le même modèle mais avec des conditions différentes.

Faire ces séparément est facile, deux sont trivial et le dernier est plus compliqué:

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

Le problème est que cela fonctionne pour tous les utilisateurs et il y a beaucoup d'entre eux. Au final, cela génère plus de 300 requêtes DB et j'aimerais les réduire au minimum.

Jusqu'à présent, j'ai venu avec ceci:

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

Ce regroupera deux premiers chefs d'accusation, les retourner et un seul SELECT sera effectuée sur la base de données. Y at-il un moyen d'incorporer le dernier appel count() dans ce même aggregate()?

J'ai essayé beaucoup de choses, mais cela semble impossible. Dois-je juste écrire un SELECT personnalisé et utiliser Item.objects.raw()?

J'ai aussi remarqué que l'exécution du aggregate() et le dernier count() est plus rapide sur ma machine de développement et SQLite que sur le serveur de mise en scène avec Postgresql, qui est un peu étrange, mais ce n'est pas ma principale préoccupation en ce moment.

Répondre

1

Étant donné que vous avez besoin des comptes pour chaque élément de votre QuerySet, vous devez utiliser annotate au lieu de l'agrégat, cela ne fera qu'effectuer une requête.

La meilleure façon pour compter les objets liés basé sur une condition est d'utiliser 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

Oh, c'est brillant. Oui, l'annotation de chaque utilisateur avec le nombre d'éléments est le chemin à parcourir. J'étais trop concentré sur les objets eux-mêmes. Merci! – BigWhale