Django annotate() 多次导致错误答案

Posted

技术标签:

【中文标题】Django annotate() 多次导致错误答案【英文标题】:Django annotate() multiple times causes wrong answers 【发布时间】:2010-11-18 21:47:57 【问题描述】:

Django 为查询集提供了很棒的新 annotate() 函数。但是,对于单个查询集中的多个注释,我无法使其正常工作。

例如,

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

一个游览可以包含多个游览评论和历史条目。我正在尝试获取这次巡演中存在多少 cmets 和历史条目。结果

history__count and tourcomment__count

值将不正确。如果只有一个 annotate() 调用,则该值将是正确的。

这两个LEFT OUTER JOINs 似乎有某种乘法效应。例如,如果游览有 3 个历史记录和 3 个 cmets,则 9 将是两者的计数值。 12 个历史记录 + 1 条评论 = 12 个值。 1 条历史记录 + 0 条评论 = 1 条历史记录,0 个 cmets(这个恰好返回正确的值)。

生成的 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

感谢您的帮助。过去,*** 帮我解决了很多已经回答的问题,但我还没有找到这个问题的答案。

【问题讨论】:

【参考方案1】:

感谢您的评论。这并不完全奏效,但它引导我朝着正确的方向前进。我终于能够通过在两个 Count() 调用中添加 distinct 来解决这个问题:

Count('tourcomment', distinct=True)

【讨论】:

...这仍然是一个可怕的解决方案,因为这只是从巨大的结果中过滤掉所有重复项 这也只适用于 Count。我在计数和总和方面遇到了类似的问题,虽然将 distinct 设置为 true 可以保持计数准确,但总和仍在相乘 另外,对于 Sum 错误描述,请参阅:code.djangoproject.com/ticket/10060 可爱,你不知道我经历过的挣扎。非常感谢。【参考方案2】:
tour_list = Tour.objects.all().annotate(tour_count=Count('tourcomment',distinct=True) ).annotate(history_count=Count('history',distinct=True) )

您必须添加distinct=True 才能获得正确的结果,否则它将返回错误的答案。

【讨论】:

【参考方案3】:

我不能保证这会解决您的问题,但请尝试将 .order_by() 添加到您的通话中。那就是:

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

原因是 django 需要选择 ORDER BY 子句中的所有字段,否则会导致选择相同的结果。通过附加 .order_by(),您将完全删除 ORDER BY 子句,从而防止这种情况发生。有关此问题的更多信息,请参阅the aggregation documentation。

【讨论】:

以上是关于Django annotate() 多次导致错误答案的主要内容,如果未能解决你的问题,请参考以下文章

django数据查询优化annotate和aggregate

Django annotate 返回了多个

使用 annotate() 包含一个 django 模型对象属性

Django:仅使用 annotate() 和 values() 计算非空 CharField

072:Django数据库ORM聚合函数详解-aggregate和annotate

Django基础aggregate和annotate方法使用详解与示例