Как «массовое обновление» с Django?

163

Я хотел бы обновить таблицу с помощью Django - что-то вроде этого в сыром SQL:

update tbl_name set name = 'foo' where name = 'bar'

Мой первый результат примерно такой - но это противно, не так ли?

list = ModelClass.objects.filter(name = 'bar')
for obj in list:
    obj.name = 'foo'
    obj.save()

Есть ли более элегантный способ?

Томас Шварцль
источник
1
Возможно, вы ищете пакетную вставку. Взгляните на stackoverflow.com/questions/4294088/…
Pramod
Я не люблю вставлять новые данные - просто обновлять существующие.
Томас Шварцль
3
Может быть, с помощью select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Юре С.
Что не противно в ModelClassподходе? Затем
подпишитесь

Ответы:

256

Обновить:

В версии Django 2.2 теперь есть параметр bulk_update .

Старый ответ:

Обратитесь к следующему разделу документации django

Обновление нескольких объектов одновременно

Короче говоря, вы должны быть в состоянии использовать:

ModelClass.objects.filter(name='bar').update(name="foo")

Вы также можете использовать Fобъекты для увеличения количества строк:

from django.db.models import F
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

Смотрите документацию .

Однако обратите внимание, что:

  • Это не будет использовать ModelClass.saveметод (поэтому, если у вас есть какая-то логика внутри, он не будет запущен).
  • Сигналы Django не будут излучаться.
  • Вы не можете выполнить .update()на нарезанный QuerySet, он должен быть на оригинальном QuerySet , так что вы должны будете опираться на .filter()и .exclude()методе.
ДБ.
источник
27
Следует также отметить , что , как следствие , не используя save(), DateTimeFieldполя с auto_now=True( «модифицированными» столбцов) не будет обновляться.
Артур
3
Но ModelClass.objects.filter(name = 'bar').update(name="foo")не выполняет цель массового обновления, если у меня есть разные данные для разных идентификаторов, как я могу сделать это без использования цикла?
Шашанк
@shihon Я не уверен, правильно ли я вас понял, но я добавил пример к ответу.
JB.
@Shashank вы уже нашли решение для вашего дела? У меня тоже такой же сценарий.
Сурав Прем
F-объекты нельзя использовать для ссылки на разные модели в методе .update ... например, вы не можете использовать Entry.objects.all().update(title=F('blog__title')). Документы имеют небольшое упоминание об этом. Если вы хотите получить данные из другой модели для обновления записей, вам нужно запустить цикл for
sean.hudson
31

Рассмотрите возможность использования django-bulk-updateнайденного здесь на GitHub .

Установка: pip install django-bulk-update

Реализация: (код взят из проектов ReadMe файла)

from bulk_update.helper import bulk_update

random_names = ['Walter', 'The Dude', 'Donny', 'Jesus']
people = Person.objects.all()

for person in people:
    r = random.randrange(4)
    person.name = random_names[r]

bulk_update(people)  # updates all columns using the default db

Обновление: как отмечает Марк в комментариях, это не подходит для одновременного обновления тысяч строк. Хотя он подходит для небольших партий от 10 до 100 лет. Размер пакета, который вам подходит, зависит от вашего процессора и сложности запросов. Этот инструмент больше похож на тачку, чем на самосвал.

Ну Эверест
источник
16
Я попробовал django-bulk-update, и я лично не рекомендую его использовать. Внутри он создает единый оператор SQL, который выглядит следующим образом: UPDATE "таблица" SET "поле" = CASE "id" КОГДА% s, ТО% s, КОГДА% s, ТО% s [[]] ГДЕ id in ( % s,% s, [...]) ;. Это нормально для нескольких строк (когда массовое обновление не требуется), но при 10000 запрос настолько сложен, что postgres тратит больше времени на процессор при понимании запроса на 100%, чем на сохранение записи на диск ,
Марк Гарсия
1
@MarcGarcia хороший момент. Я обнаружил, что многие разработчики используют внешние библиотеки, не зная их влияния
Dejell
3
@MarcGarcia Я не согласен с тем, что массовое обновление не является ценным и действительно необходимо только тогда, когда необходимы тысячи обновлений. Использование его для одновременного выполнения 10 000 строк не рекомендуется по указанным выше причинам, но использование его для одновременного обновления 50 строк намного эффективнее, чем использование базы данных с 50 отдельными запросами на обновление.
Ню Эверест
3
Наилучшие решения, которые я нашел: а) использовать @action.atomic decorator, который повышает производительность за счет использования одной транзакции, или б) сделать массовую вставку во временную таблицу, а затем ОБНОВИТЬ из временной таблицы в исходную.
Марк Гарсия,
1
Я знаю, что это старая тема, но на самом деле CASE / WHERE - не единственный способ. Для PostgreSQL есть и другие подходы, но они специфичны для БД, например stackoverflow.com/a/18799497 Однако я не уверен, возможно ли это в ANSI SQL
Илиан Илиев
21

Версия Django 2.2 теперь имеет bulk_update метод ( примечания к выпуску ).

https://docs.djangoproject.com/en/stable/ref/models/querysets/#bulk-update

Пример:

# get a pk: record dictionary of existing records
updates = YourModel.objects.filter(...).in_bulk()
....
# do something with the updates dict
....
if hasattr(YourModel.objects, 'bulk_update') and updates:
    # Use the new method
    YourModel.objects.bulk_update(updates.values(), [list the fields to update], batch_size=100)
else:
    # The old & slow way
    with transaction.atomic():
        for obj in updates.values():
            obj.save(update_fields=[list the fields to update])
въелись
источник
8

Если вы хотите установить одно и то же значение в коллекции строк , вы можете использовать метод update () в сочетании с любым термином запроса, чтобы обновить все строки в одном запросе:

some_list = ModelClass.objects.filter(some condition).values('id')
ModelClass.objects.filter(pk__in=some_list).update(foo=bar)

Если вы хотите обновить коллекцию строк с различными значениями в зависимости от некоторых условий, вы можете в лучшем случае пакетировать обновления в соответствии со значениями. Допустим, у вас есть 1000 строк, в которых вы хотите установить для столбца одно из значений X, затем вы можете заранее подготовить пакеты, а затем только выполнить X запросов на обновление (каждая по сути имеет форму первого примера выше) + начальный SELECT -query.

Если каждая строка требует уникального значения, нет способа избежать одного запроса на обновление. Возможно, посмотрите на другие архитектуры, такие как CQRS / Event Sourcing, если вам нужна производительность в этом последнем случае.

Андреас Бергстрем
источник
1

IT возвращает количество объектов, обновленных в таблице.

update_counts = ModelClass.objects.filter(name='bar').update(name="foo")

Вы можете обратиться по этой ссылке, чтобы получить больше информации о массовом обновлении и создании. Массовое обновление и создание

Шивам Шарма
источник