¿Cómo 'actualizar en masa' con Django?

163

Me gustaría actualizar una tabla con Django, algo así en SQL sin formato:

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

Mi primer resultado es algo como esto, pero eso es desagradable, ¿no?

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

¿Hay alguna forma más elegante?

Thomas Schwärzl
fuente
1
Es posible que esté buscando inserción por lotes. Eche un vistazo a stackoverflow.com/questions/4294088/…
Pramod
No me gusta insertar datos nuevos, solo actualizar los existentes.
Thomas Schwärzl
3
Tal vez con la ayuda de select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.
¿Qué no es desagradable del ModelClassenfoque? Luego alimente a Django como: stackoverflow.com/questions/16853649/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

256

Actualizar:

La versión 2.2 de Django ahora tiene una actualización masiva .

Vieja respuesta:

Consulte la siguiente sección de documentación de django

Actualización de múltiples objetos a la vez

En resumen, deberías poder usar:

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

También puede usar Fobjetos para hacer cosas como incrementar filas:

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

Ver la documentación .

Sin embargo, tenga en cuenta que:

  • Esto no usará el ModelClass.savemétodo (por lo tanto, si tiene algo de lógica dentro, no se activará).
  • No se emitirán señales django.
  • No puede realizar un .update()QuerySet en rodajas, debe estar en un QuerySet original, por lo que deberá apoyarse en los métodos .filter()y .exclude().
jb.
fuente
27
Tenga en cuenta también que, como consecuencia de no usar save(), los DateTimeFieldcampos con auto_now=True(columnas "modificadas") no se actualizarán.
Arthur
3
Pero ModelClass.objects.filter(name = 'bar').update(name="foo")no cumple el propósito de la actualización masiva, si tengo datos diferentes para diferentes identificadores, ¿cómo podría hacerlo sin usar el bucle?
Shashank
@shihon No estoy seguro si te entendí bien, pero agregué un ejemplo a la respuesta.
jb.
@Shashank, ¿ya has encontrado alguna solución para tu caso? También estoy teniendo el mismo escenario.
Sourav Prem
Los objetos F no se pueden usar para hacer referencia a diferentes modelos en el método .update ... por ejemplo, no se puede usar Entry.objects.all().update(title=F('blog__title')). Los doctores tienen una pequeña mención de esto. Si desea obtener datos de otro modelo para actualizar sus entradas, deberá ejecutar un bucle for
sean.hudson el
31

Considere usar django-bulk-updateencontrado aquí en GitHub .

Instalar en pc: pip install django-bulk-update

Implementar: (código tomado directamente del archivo Léame de proyectos)

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

Actualización: como Marc señala en los comentarios, esto no es adecuado para actualizar miles de filas a la vez. Aunque es adecuado para lotes más pequeños de 10 a 100. El tamaño del lote adecuado para usted depende de su CPU y la complejidad de la consulta. Esta herramienta se parece más a una carretilla que a un camión volquete.

nu everest
fuente
16
Intenté django-bulk-update, y personalmente desaconsejo usarlo. Lo que hace internamente es crear una sola instrucción SQL que se vea así: ACTUALIZAR el "campo" SET "de la tabla" = CASO "id" CUANDO% s ENTONCES% s CUANDO% s ENTONCES [...]% s EN DONDE id ( % s,% s, [...]) ;. Esto está bastante bien para algunas filas (cuando no se necesita un actualizador masivo), pero con 10,000, la consulta es tan compleja que Postgres pasa más tiempo con la CPU al 100% entendiendo la consulta, que el tiempo que ahorra escribiendo en el disco .
Marc Garcia
1
@MarcGarcia buen punto. Descubrí que muchos desarrolladores usan bibliotecas externas sin conocer su impacto
Dejell
3
@MarcGarcia No estoy de acuerdo con que la actualización masiva no sea valiosa y solo sea realmente necesaria cuando se necesitan miles de actualizaciones. No es aconsejable usarlo para hacer 10,000 filas a la vez por las razones que mencionó, pero usarlo para actualizar 50 filas a la vez es mucho más eficiente que presionar la base de datos con 50 solicitudes de actualización separadas.
nu everest
3
Las mejores soluciones que encontré son: a) usar el decorador @ transaccional.atómico, que mejora el rendimiento al usar una sola transacción, o b) hacer una inserción masiva en una tabla temporal, y luego una ACTUALIZACIÓN de la tabla temporal a la original.
Marc Garcia
1
Sé que este es un hilo viejo, pero en realidad CASO / DONDE no es la única forma. Para PostgreSQL hay otros enfoques, pero son específicos de la base de datos, por ejemplo stackoverflow.com/a/18799497 Sin embargo, no estoy seguro de si esto es posible en ANSI SQL
Ilian Iliev
21

La versión 2.2 de Django ahora tiene un bulk_updatemétodo ( notas de la versión ).

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

Ejemplo:

# 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
fuente
1
De hecho, aparece en las notas de la versión 2.2
Benoit Blanchon el
8

Si desea establecer el mismo valor en una colección de filas , puede usar el método update () combinado con cualquier término de consulta para actualizar todas las filas en una consulta:

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

Si desea actualizar una colección de filas con diferentes valores dependiendo de alguna condición, en el mejor de los casos puede agrupar las actualizaciones según los valores. Digamos que tiene 1000 filas en las que desea establecer una columna en uno de los valores X, luego puede preparar los lotes de antemano y luego solo ejecutar X consultas de actualización (cada una esencialmente con la forma del primer ejemplo anterior) + el SELECT inicial -consulta.

Si cada fila requiere un valor único, no hay forma de evitar una consulta por actualización. Tal vez busque otras arquitecturas como CQRS / fuente de eventos si necesita rendimiento en este último caso.

Andreas Bergström
fuente
1

TI devuelve el número de objetos que se actualizan en la tabla.

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

Puede consultar este enlace para obtener más información sobre la actualización y creación masiva. Actualización masiva y Crear

shivam sharma
fuente