Django annotate() несколько раз вызывает неправильные ответы

В Django есть отличная новая функция annotate() для наборов запросов. Однако я не могу заставить его работать должным образом для нескольких аннотаций в одном наборе запросов.

Например,

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

Тур может содержать несколько комментариев к туру и записи истории. Я пытаюсь получить, сколько комментариев и записей истории существует для этого тура. Результирующий

history__count and tourcomment__count

значения будут неверными. Если есть только один вызов annotate(), значение будет правильным.

Кажется, есть какой-то мультипликативный эффект от двух LEFT OUTER JOIN. Например, если у тура есть 3 истории и 3 комментария, 9 будет значением счетчика для обоих. 12 историй + 1 комментарий = 12 для обоих значений. 1 история + 0 комментариев = 1 история, 0 комментариев (этот случай возвращает правильные значения).

Результирующий вызов SQL:

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

Я попытался объединить результаты двух наборов запросов, содержащих один вызов annotate(), но это работает неправильно... Вы не можете гарантировать, что порядок будет таким же. и это кажется слишком сложным и запутанным, поэтому я искал что-то лучшее...

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

Спасибо за любую помощь. Stackoverflow спас мою задницу в прошлом благодаря множеству вопросов, на которые уже были даны ответы, но я пока не смог найти ответ на этот.


person tbak    schedule 12.08.2009    source источник


Ответы (3)


Спасибо за ваш комментарий. Это не совсем сработало, но направило меня в правильном направлении. Наконец-то я смог решить эту проблему, добавив отдельные вызовы к обоим вызовам Count():

Count('tourcomment', distinct=True)
person tbak    schedule 12.08.2009
comment
... что по-прежнему является ужасным решением, поскольку оно просто отфильтровывает все дубликаты из огромного результата - person dragonroot; 08.01.2014
comment
Это также работает только с Count. У меня аналогичная проблема с подсчетом и суммой, и хотя установка для отдельного значения true сохраняет точность подсчета, сумма все еще умножается. - person StephenTG; 19.03.2014
comment
Кроме того, для описания ошибки Sum см.: code.djangoproject.com/ticket/10060 - person sobolevn; 21.09.2015

Я не могу гарантировать, что это решит вашу проблему, но попробуйте добавить .order_by() к вашему вызову. Это:

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

Причина этого в том, что django необходимо выбрать все поля в предложении ORDER BY, что приводит к выбору идентичных результатов. Добавляя .order_by(), вы полностью удаляете предложение ORDER BY, что предотвращает это. См. агрегацию документации для получения дополнительной информации по этому вопросу.

person Cide    schedule 12.08.2009

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

Вы должны добавить distinct=True, чтобы получить правильный результат, иначе он вернет неправильный ответ.

person Javed Gouri    schedule 15.04.2019