Como 'atualização em massa' com o Django?

163

Gostaria de atualizar uma tabela com o Django - algo parecido com isto em SQL bruto:

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

Meu primeiro resultado é algo assim - mas isso é desagradável, não é?

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

Existe uma maneira mais elegante?

Thomas Schwärzl
fonte
1
Você pode estar procurando inserção em lote. Dê uma olhada em stackoverflow.com/questions/4294088/…
Pramod
Não gosto de inserir novos dados - basta atualizar os existentes.
Thomas Schwärzl
3
Talvez com a ajuda de select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.
O que não é desagradável nessa ModelClassabordagem? Em seguida, alimente o Django como: stackoverflow.com/questions/16853649/…
Ciro Santilli #

Respostas:

256

Atualizar:

A versão do Django 2.2 agora tem um bulk_update .

Resposta antiga:

Consulte a seguinte seção de documentação do django

Atualizando vários objetos de uma só vez

Em suma, você deve ser capaz de usar:

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

Você também pode usar Fobjetos para fazer coisas como incrementar linhas:

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

Veja a documentação .

No entanto, observe que:

  • Isso não usará o ModelClass.savemétodo (portanto, se você tiver alguma lógica, ela não será acionada).
  • Nenhum sinal de django será emitido.
  • Você não pode executar um .update()QuerySet fatiado, ele deve estar em um QuerySet original, portanto, você precisará se apoiar nos métodos .filter()e .exclude().
jb.
fonte
27
Observe também que, como consequência do não uso save(), os DateTimeFieldcampos com auto_now=True(colunas "modificadas") não serão atualizados.
Arthur
3
Mas ModelClass.objects.filter(name = 'bar').update(name="foo")não cumpre o objetivo da atualização em massa, se eu tiver dados diferentes para diferentes IDs, como eu poderia fazer isso sem usar o loop?
Shashank
@shihon Não sei se entendi direito, mas adicionei exemplo à resposta.
jb.
@ Shanks já encontrou alguma solução para o seu caso? Eu também estou tendo o mesmo cenário.
Sourav Prem
Objetos F não podem ser usados ​​para referenciar modelos diferentes no método .update ... por exemplo, você não pode usar Entry.objects.all().update(title=F('blog__title')). Os documentos têm uma pequena menção a isso. Se você quiser extrair dados de um outro modelo para atualizar suas entradas, você terá que executar um loop
sean.hudson
31

Considere o uso django-bulk-updateencontrado aqui no GitHub .

Instalar: pip install django-bulk-update

Implementar: (código extraído diretamente do arquivo Leiame dos projetos)

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

Atualização: Como Marc aponta nos comentários, isso não é adequado para atualizar milhares de linhas de uma só vez. Embora seja adequado para lotes menores de 10 a 100. O tamanho do lote certo para você depende da CPU e da complexidade da consulta. Essa ferramenta é mais um carrinho de mão do que um caminhão de lixo.

nu everest
fonte
16
Eu tentei o django-bulk-update e pessoalmente desencorajo a usá-lo. O que ele faz internamente é criar uma única instrução SQL parecida com esta: UPDATE "table" SET "field" = CASE "id" WHEN% s THEN% s WHEN% s THEN% s THEN% s [...] WHERE id in ( % s,% s, [...]) ;. Isso é bom para poucas linhas (quando o atualizador em massa não é necessário), mas com 10.000, a consulta é tão complexa que o postgres passa mais tempo com a CPU 100% entendendo a consulta do que o tempo que economiza gravando no disco .
Marc Garcia
1
@MarcGarcia good point. Eu encontrei muitos desenvolvedores usar bibliotecas externas sem saber o seu impacto
Dejell
3
@ MarcGarcia Eu discordo que a atualização em massa não é valiosa e só é realmente necessária quando milhares de atualizações são necessárias. Não é aconselhável usá-lo para executar 10.000 linhas de uma só vez pelos motivos mencionados, mas usá-lo para atualizar 50 linhas de uma só vez é muito mais eficiente do que atingir o banco de dados com 50 solicitações de atualização separadas.
nu everest
3
As melhores soluções que encontrei são: a) use o decorador @ transaction.atomic, que melhora o desempenho usando uma única transação, ou b) faça uma inserção em massa em uma tabela temporária e, em seguida, faça uma atualização da tabela temporária para a original.
Marc Garcia
1
Eu sei que esse é um tópico antigo, mas, na verdade, CASE / WHERE não é o único caminho. Para PostgreSQL existem outras abordagens, mas eles são DB específico, por exemplo stackoverflow.com/a/18799497 No entanto, eu não tenho certeza se isso é possível no ANSI SQL
Ilian Iliev
21

A versão do Django 2.2 agora possui um bulk_updatemétodo ( notas de versão ).

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

Exemplo:

# 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])
velis
fonte
8

Se você deseja definir o mesmo valor em uma coleção de linhas , pode usar o método update () combinado com qualquer termo de consulta para atualizar todas as linhas em uma consulta:

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

Se você deseja atualizar uma coleção de linhas com valores diferentes, dependendo de alguma condição, na melhor das hipóteses, é possível agrupar as atualizações de acordo com os valores. Digamos que você tenha 1000 linhas nas quais deseja definir uma coluna com um dos valores X, então você pode preparar os lotes antecipadamente e executar apenas X consultas de atualização (cada uma essencialmente com a forma do primeiro exemplo acima) + o SELECT inicial -inquerir.

Se todas as linhas exigirem um valor exclusivo, não há como evitar uma consulta por atualização. Talvez procure outras arquiteturas como CQRS / Event sourcing, se precisar de desempenho neste último caso.

Andreas Bergström
fonte
1

O número de objetos retornados pela TI é atualizado na tabela.

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

Você pode consultar este link para obter mais informações sobre atualização e criação em massa. Atualização em massa e Criar

shivam sharma
fonte