28 votes

Django annotate() plusieurs fois provoque des réponses erronées

Django dispose de l'excellente nouvelle fonction annotate() pour les querysets. Cependant, je n'arrive pas à la faire fonctionner correctement pour plusieurs annotations dans un seul queryset.

Par exemple,

tour_list = Tour.objects.all().annotate( Count('tourcomment') ).annotate( Count('history') )

Une visite peut contenir plusieurs entrées de commentaires et d'historique. J'essaie de savoir combien de commentaires et d'entrées d'historique existent pour cette visite. Le résultat

history__count and tourcomment__count

seront incorrectes. S'il n'y a qu'un seul appel à annotate(), la valeur sera correcte.

Il semble qu'il y ait une sorte d'effet multiplicatif provenant des deux LEFT OUTER JOIN s. Par exemple, si une visite a 3 historiques et 3 commentaires, 9 sera la valeur de comptage pour les deux. 12 historiques + 1 commentaire = 12 pour les deux valeurs. 1 historique + 0 commentaire = 1 historique, 0 commentaire (celui-ci renvoie les valeurs correctes).

L'appel SQL résultant est :

SELECT `testapp_tour`.`id`, `testapp_tour`.`operator_id`, `testapp_tour`.`name`, `testapp_tour`.`region_id`, `testapp_tour`.`description`, `testapp_tour`.`net_price`, `testapp_tour`.`sales_price`, `testapp_tour`.`enabled`, `testapp_tour`.`num_views`, `testapp_tour`.`create_date`, `testapp_tour`.`modify_date`, `testapp_tour`.`image1`, `testapp_tour`.`image2`, `testapp_tour`.`image3`, `testapp_tour`.`image4`, `testapp_tour`.`notes`, `testapp_tour`.`pickup_time`, `testapp_tour`.`dropoff_time`, COUNT(`testapp_tourcomment`.`id`) AS `tourcomment__count`, COUNT(`testapp_history`.`id`) AS `history__count` 
FROM `testapp_tour` LEFT OUTER JOIN `testapp_tourcomment` ON (`testapp_tour`.`id` = `testapp_tourcomment`.`tour_id`) LEFT OUTER JOIN `testapp_history` ON (`testapp_tour`.`id` = `testapp_history`.`tour_id`)
GROUP BY `testapp_tour`.`id`
ORDER BY `testapp_tour`.`name` ASC

J'ai essayé de combiner les résultats de deux querysets qui contiennent un seul appel à annotate (), mais cela ne fonctionne pas correctement... Vous ne pouvez pas vraiment garantir que l'ordre sera le même. et cela semble trop compliqué et désordonné donc j'ai cherché quelque chose de mieux...

tour_list = Tour.objects.all().filter(operator__user__exact = request.user ).filter(enabled__exact = True).annotate( Count('tourcomment') )
tour_list_historycount = Tour.objects.all().filter( enabled__exact = True ).annotate( Count('history') )
for i,o in enumerate(tour_list):
    o.history__count = tour_list_historycount[i].history__count

Merci pour toute aide. Stackoverflow m'a sauvé la mise par le passé avec de nombreuses questions déjà résolues, mais je n'ai pas encore trouvé de réponse à celle-ci.

60voto

tbak Points 346

Merci pour votre commentaire. Cela n'a pas tout à fait fonctionné mais m'a mis sur la bonne voie. J'ai finalement pu résoudre ce problème en ajoutant distinct aux deux appels à Count() :

Count('tourcomment', distinct=True)

1voto

Javed Gouri Points 104
tour_list = Tour.objects.all().annotate(tour_count=Count('tourcomment',distinct=True) ).annotate(history_count=Count('history',distinct=True) )

Vous devez ajouter distinct=True pour obtenir le bon résultat, sinon il retournera une mauvaise réponse.

0voto

Cide Points 1901

Je ne peux pas garantir que cela résoudra votre problème, mais essayez d'annexer .order_by() à votre appel. C'est-à-dire :

tour_list = Tour.objects.all().annotate(Count('tourcomment')).annotate(Count('history')).order_by()

La raison en est que django doit sélectionner tous les champs dans la clause ORDER BY, ce qui entraîne la sélection de résultats autrement identiques. En ajoutant .order_by() vous supprimez complètement la clause ORDER BY, ce qui empêche ce phénomène de se produire. Voir la documentation d'agrégation pour plus d'informations sur cette question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X