Django查询排除指定数据时出现异常:排除2条数据却导致结果集大幅缩减
这问题确实够诡异的!我之前在处理带关联和聚合的QuerySet时也碰到过类似的计数偏差,大概率是JOIN导致的笛卡尔积或者聚合逻辑的小问题,给你几个具体的排查方向:
1. 先确认原QuerySet的计数是否是「唯一记录数」
你打印的qs.count()是97,但因为你的QuerySet里关联了sku__reconciliation_activity(一对多关系),Django执行查询时会做JOIN操作——如果一个sku对应多条reconciliation_activity记录,一条主表数据会被拆分成多条结果,导致count出来的是JOIN后的行数,不是实际唯一的sku数量。
你可以先验证下:
# 查看去重后的真实sku数量 print(qs.distinct('sku').count())
如果这个数值接近70,那基本就是笛卡尔积导致的计数偏差,原97是重复后的行数,不是真实的待处理记录数。
2. 拆分过滤器,分步验证数据分布
你现在的排除逻辑是~Q(action=NO_ACTION, missing__gte=1),但action和missing都是从关联表annotate来的,可能存在NULL值或者关联数据异常,导致交集统计不准。建议分步查询:
# 统计所有action为NO_ACTION的记录数 print("NO_ACTION总数:", qs.filter(action=ReconciliationActivity.ActionChoices.NO_ACTION).count()) # 统计所有missing>=1的记录数 print("missing>=1总数:", qs.filter(missing__gte=1).count()) # 统计两者的交集(你认为的脏数据) print("脏数据交集:", qs.filter(action=ReconciliationActivity.ActionChoices.NO_ACTION, missing__gte=1).count())
如果前两个数值远大于你预期,说明很多记录被关联表的数据重复展开了,这时候的「脏数据数量2」其实是重复后的行数,不是真实的脏sku数量。
3. 检查annotate里的表达式错误
看你的代码里有个明显的小问题:
Sum( 'ending_warehouse_balance', filter=Q(disposition='SELLABLE') ), total_units=Sum( F('ending_warehouse_balance') + Abs('in_transit_between_warehouses') + Abs('customer_shipments') )
这里的Abs('in_transit_between_warehouses')应该写成Abs(F('in_transit_between_warehouses'))!直接传字符串的话,Django会把它当成字段名的字符串字面量,而不是字段值的表达式,这会导致total_units计算错误,进而触发~Q(total_units=0)过滤掉大量本不该被排除的记录,这可能是你得到70的直接原因!
另外,missing_subquery_count = Count(missing_subquery_count)这个写法也有问题,Count函数需要传入字段名或者聚合对象,你这里用变量名当参数,大概率是笔误,应该改成比如Count('id')或者对应Subquery的聚合字段。
4. 用去重后的sku列表验证真实数量
如果前面的排查还没找到问题,直接用sku去重后的列表来统计:
# 原QuerySet的唯一sku集合 original_skus = set(qs.values_list('sku', flat=True)) print("真实sku总数:", len(original_skus)) # 排除脏数据后的唯一sku集合 filtered_skus = set(qs.filter( ~Q(action=ReconciliationActivity.ActionChoices.NO_ACTION, missing__gte=1) ).values_list('sku', flat=True)) print("排除后sku数:", len(filtered_skus)) # 脏数据的唯一sku集合 dirty_skus = set(qs.filter( action=ReconciliationActivity.ActionChoices.NO_ACTION, missing__gte=1 ).values_list('sku', flat=True)) print("真实脏sku数:", len(dirty_skus))
这样就能明确是记录重复导致的计数偏差,还是真的有记录被错误过滤了。
内容的提问来源于stack exchange,提问作者mohamed naser

