我需要找到两个孩子(同级模型)中Tags
有差异的对象。
示例设置:
class Parent(models.Model):
pass
class Tag(models.Model):
name = models.CharField(max_length=256, unique=True)
class Child_OLD(models.Models):
parent = models.OneToOneField(Parent, ...)
tags = models.Many2ManyField(Tag)
class Child_NEW(models.Models):
parent = models.ForiegnKey(Parent, ...)
tags = models.Many2ManyField(Tag)
我想确保Child_OLD上的所有Tags
记录都在Child_NEW
记录中表示。具体来说,我想找到比Child_OLD具有Child_NEW没有的标签的任何父级,并且使用比单独检查每个父级更快的方法。
我只想找到child_old的标签不在child_new上的父母。 这是完成类似操作的循环:
diffs = []
for parent in parents:
cn_tags = Tag.objects.filter(child_new__parent=parent)
qs_diff = parent.child_old.tags.all().difference(cn_tags)
if qs.exists():
diffs.append(parent.pk)
同样,我希望以一种更优化的方式使用查询集来完成此操作,因为遍历每个父级非常慢
有大约1亿+个“父母”和大约500个唯一标签。一个典型的孩子会有0-5个标签
答案 0 :(得分:0)
一种方法是过滤Child_NEW
中与Child_OLD
中的标签相同的标签,并检查其计数是否与Child_NEW
中的所有标签的计数相同。 / p>
首先,在Child_OLD
中选择匹配标签的最里面的子查询:
criteria_sq = (Child_OLD.objects
.filter(parent=OuterRef(OuterRef('id')))
.values('tags__id')
)
此子查询然后包装在另一个子查询中,以计算Child_NEW
中的匹配标签:
select_sq = (Child_NEW.objects
.filter(id=OuterRef('child_old__id'), tags__id__in=Subquery(criteria_sq, ))
.values('parent')
.annotate(tag_cnt=Count('parent'))
.values('tag_cnt')
)
这是最终的查询集。如果有多个Parents
指向一个Child_NEW
,则可以有重复的Parent
。
qs = (Parent.objects
.annotate(tag_count_old=Count('child_old__tags'))
.annotate(tag_count_new=Subquery(select_sq, output_field=IntegerField()))
.filter(Q(tag_count_old__gt=F('tag_count_new')) | Q(tag_count_old__isnull=False, tag_count_new__isnull=True))
)
另一种解决方案是使用原始SQL在旧的子/标签中间表和新的子/标签中间表之间创建左联接,并选择在右侧具有空值的父级。在此连接的每一侧,您都需要对a)各自的子表和b)父表进行进一步的内部联接,以便可以在父表id上进行联接。