Найти все объекты определенного класса, которые не имеют активных связей с другими объектами

У меня есть класс A, который используется как внешний ключ во многих других классах.

class A(models.Model):
  pass

class B(models.Model):
  a: A = ForeignKey(A)

class C(models.Model):
  other_name: A = ForeignKey(A)

Сейчас у меня есть база данных с огромной таблицей объектов A и множеством классов, таких как B и C, которые ссылаются на A (скажем, потенциально десятки). В этой таблице много объектов (100k+), и я хочу очистить все объекты, на которые активно не ссылаются другие объекты с внешним ключом. Например, на object 1 из класса A не ссылаются классы B и C.

Как мне это сделать? Я уже придумал следующий код:

a_list: list = list()
classes: list[tuple] = [(B, "a"), (C, "other_name")]

for cl, field in classes:
  field_object: Field = cl._meta.get_field(field)
  for obj in cl.objects.all():
    a: A = field_object.value_from_object(obj)
    a_list.append(a)

to_remove: list[A] = [a for a in A.objects.all() if a not in a_list]
for a in to_remove():
  a.remove()

В связи с этим у меня возникает несколько вопросов:

  • Что если я не знаю полного списка классов и полей (случай, так как это большая группа)?
  • Является ли это наиболее эффективным способом для большой таблицы с большим количеством несвязанных объектов (скажем, 95%)? Я думаю, что я могу оптимизировать это намного больше.

Вы можете фильтровать с помощью:

A.objects.filter(b=None, c=None).delete()

Это позволит создать правильные JOIN и таким образом определить элементы за один запрос, без необходимости получения всех остальных записей модели из базы данных.

Но это в любом случае будет дорого, поскольку триггеры выполняются Django, который, таким образом, будет "собирать" все A объекты.

Если вы не знаете, на что ссылается A, вы можете работать с мета моделью, так:

from django.db.models.fields.reverse_related import OneToOneRel

fields = {
    f.related_query_name: None
    for f in A._meta.get_fields()
    if isinstance(f, ManyToOneRel)
}

A.objects.filter(**fields).delete()

Это будет искать все ForeignKey и OneToOneField от других моделей, которые направлены (непосредственно) на модель A, затем делать LEFT OUTER JOIN и фильтровать на NULL, а затем удалять их.

Я бы посоветовал сначала осмотреть A.objects.filter(**fields) однако, и убедиться, что вы не убрали все необходимые предметы.

Вернуться на верх