查找在M2M项目中具有差异的同级记录

时间:2019-05-06 17:08:29

标签: django

我需要找到两个孩子(同级模型)中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个标签

1 个答案:

答案 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上进行联接。