Django中Count函数按ID过滤与关联字段过滤返回结果不一致问题
这个差异是Django ORM处理跨外键过滤时的JOIN合并规则导致的,和Count函数本身的逻辑无关:
第一种查询
filter(waypoints__in=wp)的执行逻辑
你使用了跨外键的关联字段作为过滤条件,Django ORM会自动将Segment表和Waypoint表做INNER JOIN,此时JOIN生成的临时表只会保留关联到wp查询集内的Waypoint的行(也就是你那1条符合条件的Waypoint对应的数据行)。
后续annotate中的Count统计和过滤条件Q(waypoints__is_visited=False)会复用这同一次JOIN的临时表,最终统计的范围被限制在wp的范围内,所以输出为1。
你可以打印seg.query查看生成的SQL,会发现JOIN条件同时包含了waypoints.id IN (你传入的wp的ID列表)和waypoints.is_visited = False两个约束。第二种查询
filter(id__in=segm_id)的执行逻辑
你直接使用Segment自身的主键ID过滤,不会触发和Waypoint表的JOIN。后续annotate统计关联Waypoint时,ORM会单独发起针对该Segment所有关联Waypoint的统计,过滤条件只校验is_visited=False,所以会统计到该Segment下全部2条未访问的Waypoint,输出为2。
如果需要第一种写法也能统计到Segment下所有未访问的Waypoint,可以将过滤逻辑改为子查询的形式,避免主查询关联Waypoint导致统计范围被缩小:
seg = Segment.objects.filter( id__in=Segment.objects.filter(waypoints__in=wp).values('id'), is_completed=False ).annotate( wp_to_visit=Count('waypoints', filter=to_visit_filter) )
此时统计结果就会和第二种写法一致。
内容的提问来源于stack exchange,提问作者Alessandro Salvetti

