Как обновить большое количество записей по частям?

У меня есть модели:

class Partner(models.Model):
    sap_code = models.CharField(max_length=64, null=True, blank=True, verbose_name='sap id', default=uuid4)
    # another fields


class DeficienciesAct(models.Model):
    partner = models.ForeignKey('partners.Partner', null=True, on_delete=models.CASCADE)
    sap_code = models.CharField(max_length=64, null=True)
    # another fields

Данные:

  • Партнер:
id sap_code
1 123
2 124
... ...
  • НедостаткиАкт:
id partner_id sap_code
1 null 123
2 null 333
... ... ...
500000 null 421

Представим, что в DeficienciesAct имеется 500 000 записей. Не все DeficienciesAct.sap_code могут быть в Partner.sap_code.

Так что мне нужно обновить поле DeficienciesAct.partner_id по DeficienciesAct.sap_code. DeficienciesAct.sap_code равен Partner.sap_code. Для этого я разработал скрипт:

DeficienciesAct.objects.filter(
        partner__isnull=True, sap_code__in=Subquery(Partner.objects.values_list("sap_code"))
    ).annotate(
        new_partner_id=Subquery(
            Partner.objects.filter(
                sap_code=OuterRef('sap_code')
            ).values('id')[:1]
        )
    ).update(partner_id=F("new_partner_id"))

Хорошо, что это работает. Но я боюсь, что огромное количество записей может повлиять на базу данных (postgres).

Есть ли способ выполнять задание частями?

Пожалуйста, не предлагайте менять модели/таблицы.

Добавьте индекс на sap_code поле с db_index=True [Django-doc], например:

class Partner(models.Model):
    sap_code = models.CharField(
        max_length=64,
        null=True,
        blank=True,
        verbose_name='sap id',
        default=uuid4,
        db_index=True,
    )
    # another fields


class DeficienciesAct(models.Model):
    partner = models.ForeignKey(
        'partners.Partner', null=True, on_delete=models.CASCADE
    )
    sap_code = models.CharField(max_length=64, null=True)

это увеличит количество поисков для данного sap_code.

Но если sap_code является уникальным полем для ForeignKey, мы можем просто использовать его в качестве значения для ForeignKey, с:

class Partner(models.Model):
    sap_code = models.CharField(
        max_length=64,
        verbose_name='sap id',
        default=uuid4,
        db_index=True,
        unique=True,
    )


class DeficienciesAct(models.Model):
    partner = models.ForeignKey(
        'partners.Partner', null=True, on_delete=models.CASCADE
    )
    sap_code = models.CharField(max_length=64, null=True)
    new_partner = models.ForeignKey(
        'partners.Partner', null=True, to_field='sap_code'
    )

Затем мы можем работать с:

DeficienciesAct.objects.update(new_partner_id=F('sap_code'))

и в конечном итоге избавиться от «старого» поля partner, а также переименовать new_parter в Partner и избавиться от поля sap_code в DeficienciesAct. Это связано с тем, что Django использует поле sap_code для ссылки на Partner.

Это сработало:

batch_size = 1000
deficiency_acts_for_update = DeficienciesAct.objects.filter(
    partner__isnull=True, sap_code__in=Subquery(Partner.objects.values_list("sap_code"))
).order_by('id')
while True:
    if not deficiency_acts_for_update.count():
        break
    deficiency_acts_for_update.filter(
        id__in=Subquery(deficiency_acts_for_update.values_list("id")[:batch_size])
    ).annotate(
        _partner_id=Subquery(
            Partner.objects.filter(
                sap_code=OuterRef('sap_code')
            ).values('id')[:1]
        )
    ).update(partner_id=F("_partner_id"))
Вернуться на верх