Come "aggiornare in blocco" con Django?


163

Vorrei aggiornare una tabella con Django - qualcosa del genere in SQL grezzo:

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

Il mio primo risultato è qualcosa del genere - ma è brutto, vero?

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

C'è un modo più elegante?


1
Potresti cercare l'inserimento batch. Dai un'occhiata a stackoverflow.com/questions/4294088/…
Pramod

Non mi piace inserire nuovi dati - basta aggiornare esistenti.
Thomas Schwärzl,

3
Forse con l'aiuto di select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.

Cosa non è brutto ModelClassnell'approccio? Poi mangimi per Django come: stackoverflow.com/questions/16853649/...
Ciro Santilli郝海东冠状病六四事件法轮功

Risposte:


256

Aggiornare:

La versione di Django 2.2 ora ha un bulk_update .

Vecchia risposta:

Fare riferimento alla seguente sezione della documentazione di Django

Aggiornamento di più oggetti contemporaneamente

In breve dovresti essere in grado di usare:

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

Puoi anche usare Foggetti per fare cose come incrementare le righe:

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

Vedere la documentazione di .

Tuttavia, si noti che:

  • Questo non utilizzerà il ModelClass.savemetodo (quindi se hai della logica al suo interno non verrà attivato).
  • Non verranno emessi segnali django.
  • Non è possibile eseguire un .update()su un QuerySet affettato, deve essere su un QuerySet originale, quindi è necessario appoggiarsi ai metodi .filter()e .exclude().

27
Inoltre, come conseguenza del mancato utilizzo save(), i DateTimeFieldcampi con auto_now=True(colonne "modificate") non verranno aggiornati.
Arthur,

3
Ma ModelClass.objects.filter(name = 'bar').update(name="foo")non soddisfa lo scopo dell'aggiornamento in blocco, se ho dati diversi per ID diversi, come potrei farlo senza utilizzare il ciclo?
Shashank,

@shihon Non sono sicuro di aver capito bene, ma ho aggiunto un esempio alla risposta.
jb.

@Shan Grazie hai già trovato una soluzione per il tuo caso? Sto anche avendo lo stesso scenario.
Sourav Prem,

Gli oggetti F non possono essere utilizzati per fare riferimento a diversi modelli nel metodo .update ... ad esempio non è possibile utilizzare Entry.objects.all().update(title=F('blog__title')). I documenti ne hanno una piccola menzione. Se si desidera estrarre dati da un altro modello per aggiornare le voci, è necessario eseguire un ciclo for
sean.hudson

31

Prendi in considerazione l'utilizzo django-bulk-updatetrovato qui su GitHub .

Installare: pip install django-bulk-update

Implementazione: (codice prelevato direttamente dal file Leggimi dei progetti)

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

Aggiornamento: come sottolinea Marc nei commenti, questo non è adatto per l'aggiornamento di migliaia di righe contemporaneamente. Sebbene sia adatto per piccoli lotti da 10 a 100. La dimensione del batch giusto per te dipende dalla CPU e dalla complessità della query. Questo strumento è più simile a una carriola che a un autocarro con cassone ribaltabile.


16
Ho provato django-bulk-update e mi scoraggio personalmente ad usarlo. Quello che fa internamente è creare una singola istruzione SQL che assomigli a questa: AGGIORNA "tabella" SET "campo" = CASE "id" QUANDO% s THEN% s QUANDO% s THEN% s [...] DOVE ID in ( % s,% s, [...]) ;. Questo è un po 'giusto per poche righe (quando non è necessario l'aggiornamento di massa), ma con 10.000, la query è così complessa, che postgres trascorre più tempo con la CPU al 100% a comprendere la query, rispetto al tempo che risparmia scrivendo sul disco .
Marc Garcia,

1
@MarcGarcia buon punto. Ho scoperto che molti sviluppatori usano librerie esterne senza conoscerne l'impatto
Dejell,

3
@MarcGarcia Non sono d'accordo sul fatto che l'aggiornamento in blocco non sia prezioso e sia realmente necessario solo quando sono necessari migliaia di aggiornamenti. Usarlo per fare 10.000 righe contemporaneamente non è consigliabile per i motivi menzionati, ma usarlo per aggiornare 50 righe contemporaneamente è molto più efficiente che colpire il db con 50 richieste di aggiornamento separate.
nu everest,

3
Le migliori soluzioni che ho trovato sono: a) use @ transaction.atomic decorator, che migliora le prestazioni utilizzando una singola transazione, oppure b) effettua un inserimento in blocco in una tabella temporanea, quindi un AGGIORNAMENTO dalla tabella temporanea a quella originale.
Marc Garcia,

1
So che questo è un vecchio thread, ma in realtà CASE / WHERE non è l'unico modo. Per PostgreSQL ci sono altri approcci, ma sono specifici del DB, ad esempio stackoverflow.com/a/18799497 Tuttavia non sono sicuro che ciò sia possibile in ANSI SQL
Ilian Iliev

21

La versione di Django 2.2 ora ha un bulk_updatemetodo ( note di rilascio ).

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

Esempio:

# 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

Se si desidera impostare lo stesso valore su una raccolta di righe , è possibile utilizzare il metodo update () combinato con qualsiasi termine di query per aggiornare tutte le righe in una query:

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

Se lo desidera aggiornare una raccolta di righe con valori diversi a seconda di alcune condizioni, nel migliore dei casi è possibile eseguire il batch degli aggiornamenti in base ai valori. Supponiamo che tu abbia 1000 righe in cui desideri impostare una colonna su uno dei valori X, quindi puoi preparare in anticipo i batch e quindi eseguire solo X query di aggiornamento (ognuna essenzialmente con la forma del primo esempio sopra) + il SELECT iniziale -query.

Se ogni riga richiede un valore univoco, non è possibile evitare una query per aggiornamento. Potresti esaminare altre architetture come CQRS / Event sourcing se hai bisogno di prestazioni in quest'ultimo caso.


1

Il numero di oggetti restituito dall'IT viene aggiornato nella tabella.

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

È possibile fare riferimento a questo collegamento per ottenere ulteriori informazioni sull'aggiornamento in blocco e creare. Aggiornamento collettivo e Crea

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.