Найти родственные записи, которые имеют различия в элементах M2M - PullRequest
3 голосов
/ 06 мая 2019

Мне нужно найти объекты, у которых есть различия в 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)

Я хочу убедиться, что все Tagsзаписи на Child_OLD представлены в записи 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)

Опять же, я собираюсь сделать это с набором запросов, более оптимизированным способом, так как итерация по каждому родителю очень медленная

Есть ~ 100 миллионов + "родители" и ~ 500 уникальных тегов.Типичный ребенок будет иметь 0-5 тегов

1 Ответ

0 голосов
/ 07 мая 2019

Один из способов - отфильтровать теги в Child_NEW, идентичные тегам в Child_OLD, и проверить, совпадает ли их количество с количеством всех тегов в Child_NEW.

. Сначаласамый внутренний подзапрос для выбора подходящих тегов в 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 для создания левого соединения между старымпромежуточная таблица child / tags и промежуточная таблица new child / tags, а также выбор родителей с нулевыми значениями справа.На каждой стороне этого соединения вам понадобятся дополнительные внутренние объединения для a) соответствующей дочерней таблицы и b) родительской таблицы, чтобы вы могли присоединиться к идентификатору родительской таблицы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...