Как обновить большое количество записей по частям?
У меня есть модели:
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"))