Wie kann ich mit Django ein Bulk-Update durchführen?

163

Ich möchte eine Tabelle mit Django aktualisieren - so etwas in Raw SQL:

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

Mein erstes Ergebnis ist so etwas - aber das ist böse, nicht wahr?

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

Gibt es einen eleganteren Weg?

Thomas Schwärzl
quelle
1
Möglicherweise suchen Sie nach einer Stapeleinfügung. Werfen
Pramod
Ich möchte keine neuen Daten einfügen - aktualisieren Sie einfach vorhandene.
Thomas Schwärzl
3
Vielleicht mit Hilfe von select_for_update? docs.djangoproject.com/de/dev/ref/models/querysets/…
Jure C.
Was ist nicht böse an der ModelClassHerangehensweise? Dann füttere
Ciro Santilli 13 冠状 病 六四 事件 13

Antworten:

256

Aktualisieren:

Die Version Django 2.2 hat jetzt ein Bulk-Update .

Alte Antwort:

Weitere Informationen finden Sie im folgenden Abschnitt zur Django-Dokumentation

Mehrere Objekte gleichzeitig aktualisieren

Kurz gesagt, Sie sollten in der Lage sein:

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

Sie können FObjekte auch zum Inkrementieren von Zeilen verwenden:

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

Siehe die Dokumentation .

Beachten Sie jedoch Folgendes:

  • Dies verwendet keine ModelClass.saveMethode (wenn Sie also eine Logik haben, wird diese nicht ausgelöst).
  • Es werden keine Django-Signale ausgegeben.
  • Sie können .update()ein QuerySet nicht in Scheiben schneiden, es muss sich in einem ursprünglichen QuerySet befinden, sodass Sie sich auf die Methoden .filter()und stützen müssen .exclude().
jb.
quelle
27
Beachten Sie auch , dass als Folge der nicht mit save(), DateTimeFieldFelder mit auto_now=True( „modifizierte“ Spalten) werden nicht aktualisiert.
Arthur
3
Aber ModelClass.objects.filter(name = 'bar').update(name="foo")erfüllt nicht den Zweck der Massenaktualisierung, wenn ich unterschiedliche Daten für unterschiedliche IDs habe, wie könnte ich das tun, ohne Schleife zu verwenden?
Shashank
@shihon Ich bin mir nicht sicher, ob ich dich richtig verstanden habe, aber ich habe der Antwort ein Beispiel hinzugefügt.
jb.
@Shashank hast du schon eine Lösung für deinen Fall gefunden? Ich habe auch das gleiche Szenario.
Sourav Prem
F-Objekte können nicht verwendet werden, um verschiedene Modelle in der .update-Methode zu referenzieren. Sie können sie beispielsweise nicht verwenden Entry.objects.all().update(title=F('blog__title')). Docs haben eine kleine Erwähnung davon. Wenn Sie Daten aus einem anderen Modell
abrufen
31

Erwägen Sie die Verwendung django-bulk-updatefand hier auf GitHub .

Installieren: pip install django-bulk-update

Implementieren: (Code direkt aus der ReadMe-Datei des Projekts)

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

Update: Wie Marc in den Kommentaren hervorhebt, ist dies nicht zum gleichzeitigen Aktualisieren von Tausenden von Zeilen geeignet. Obwohl es für kleinere Chargen von 10 bis 100 geeignet ist. Die Größe des für Sie geeigneten Stapels hängt von Ihrer CPU- und Abfragekomplexität ab. Dieses Werkzeug ähnelt eher einem Schubkarren als einem Muldenkipper.

nu everest
quelle
16
Ich habe versucht, Django-Bulk-Update, und ich persönlich rate davon ab, es zu verwenden. Intern wird eine einzelne SQL-Anweisung erstellt, die folgendermaßen aussieht: UPDATE "Tabelle" SET "Feld" = CASE "ID" WENN% s DANN% s WENN% s DANN% s [...] WO ID in ( % s,% s, [...]);. Dies ist für einige Zeilen in Ordnung (wenn kein Bulk-Updater benötigt wird), aber mit 10.000 ist die Abfrage so komplex, dass Postgres mehr Zeit mit der CPU verbringt, um die Abfrage zu 100% zu verstehen, als die Zeit, die beim Schreiben auf die Festplatte gespart wird .
Marc Garcia
1
@MarcGarcia guter Punkt. Ich fand, dass viele Entwickler externe Bibliotheken verwenden, ohne ihre Auswirkungen zu kennen
Dejell
3
@MarcGarcia Ich bin nicht der Meinung, dass Massenaktualisierungen nicht wertvoll sind und nur dann wirklich benötigt werden, wenn Tausende von Aktualisierungen erforderlich sind. Die Verwendung für 10.000 Zeilen auf einmal ist aus den von Ihnen genannten Gründen nicht ratsam. Die Verwendung für die gleichzeitige Aktualisierung von 50 Zeilen ist jedoch viel effizienter, als die Datenbank mit 50 separaten Aktualisierungsanforderungen zu erreichen.
Nu Everest
3
Die besten Lösungen, die ich gefunden habe, sind: a) Verwenden Sie @ transaction.atomic Decorator, um die Leistung durch Verwendung einer einzelnen Transaktion zu verbessern, oder b) Erstellen Sie eine Masseneinfügung in eine temporäre Tabelle und anschließend ein UPDATE von der temporären Tabelle zur ursprünglichen.
Marc Garcia
1
Ich weiß, dass dies ein alter Thread ist, aber eigentlich ist CASE / WHERE nicht der einzige Weg. Für PostgreSQL gibt es andere Ansätze, aber sie sind DB-spezifisch, z. B. stackoverflow.com/a/18799497. Ich bin mir jedoch nicht sicher, ob dies in ANSI SQL möglich ist
Ilian Iliev
21

Django 2.2 - Version hat jetzt ein bulk_updateVerfahren ( Release Notes ).

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

Beispiel:

# 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
quelle
1
In der Tat ist es in den Versionshinweisen für 2.2
Benoit Blanchon
8

Wenn Sie für eine Auflistung von Zeilen denselben Wert festlegen möchten , können Sie die update () -Methode in Kombination mit einem beliebigen Abfragebegriff verwenden, um alle Zeilen in einer Abfrage zu aktualisieren:

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

Wenn Sie eine Sammlung von Zeilen mit unterschiedlichen Werten abhängig von einer bestimmten Bedingung aktualisieren möchten, können Sie die Aktualisierungen im besten Fall nach Werten stapeln. Angenommen, Sie haben 1000 Zeilen, in denen Sie eine Spalte auf einen der X-Werte setzen möchten. Dann können Sie die Stapel im Voraus vorbereiten und dann nur X Update-Abfragen ausführen (jede hat im Wesentlichen die Form des ersten Beispiels oben) + das anfängliche SELECT -Abfrage.

Wenn für jede Zeile ein eindeutiger Wert erforderlich ist, kann eine Abfrage pro Aktualisierung nicht vermieden werden. Schauen Sie sich vielleicht andere Architekturen wie CQRS / Event Sourcing an, wenn Sie in letzterem Fall Leistung benötigen.

Andreas Bergström
quelle
1

IT gibt die Anzahl der Objekte zurück, die in der Tabelle aktualisiert wurden.

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

Über diesen Link erhalten Sie weitere Informationen zum Bulk-Update und zum Erstellen. Massenaktualisierung und Erstellen

Shivam Sharma
quelle