Bagaimana cara 'memperbarui secara massal' dengan Django?

163

Saya ingin memperbarui tabel dengan Django - sesuatu seperti ini di SQL mentah:

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

Hasil pertama saya adalah sesuatu seperti ini - tapi itu tidak menyenangkan, bukan?

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

Apakah ada cara yang lebih elegan?

Thomas Schwärzl
sumber
1
Anda mungkin mencari bets insert. Lihatlah stackoverflow.com/questions/4294088/…
Pramod
Saya tidak suka memasukkan data baru - cukup perbarui yang ada.
Thomas Schwärzl
3
Mungkin dengan bantuan select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.
Apa yang tidak jahat tentang ModelClasspendekatan itu? Kemudian beri makan ke Django sebagai: stackoverflow.com/questions/16853649/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

256

Memperbarui:

Versi Django 2.2 sekarang memiliki bulk_update .

Jawaban lama:

Rujuk ke bagian dokumentasi Django berikut ini

Memperbarui beberapa objek sekaligus

Singkatnya, Anda harus dapat menggunakan:

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

Anda juga dapat menggunakan Fobjek untuk melakukan hal-hal seperti menambah baris:

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

Lihat dokumentasi .

Namun, perhatikan bahwa:

  • Ini tidak akan menggunakan ModelClass.savemetode (jadi jika Anda memiliki beberapa logika di dalamnya tidak akan dipicu).
  • Tidak ada sinyal Django akan dipancarkan.
  • Anda tidak dapat melakukan .update()pada irisan QuerySet, itu harus pada QuerySet asli sehingga Anda harus bersandar pada .filter()dan .exclude()metode.
jb.
sumber
27
Perhatikan juga bahwa sebagai akibat dari tidak menggunakan save(), DateTimeFieldbidang dengan kolom auto_now=True("dimodifikasi") tidak akan diperbarui.
Arthur
3
Tetapi ModelClass.objects.filter(name = 'bar').update(name="foo")tidak memenuhi tujuan pembaruan massal, jika saya memiliki data berbeda untuk id berbeda bagaimana saya bisa melakukannya tanpa menggunakan loop?
Shashank
@shihon Saya tidak yakin apakah saya benar, tetapi saya menambahkan contoh pada jawabannya.
jb.
@Shashank, apakah Anda sudah menemukan solusi untuk kasus Anda? Saya juga mengalami skenario yang sama.
Sourav Prem
Objek F tidak dapat digunakan untuk referensi model yang berbeda dalam metode .update ... misalnya Anda tidak dapat menggunakan Entry.objects.all().update(title=F('blog__title')). Documents sedikit menyebutkan hal ini. Jika Anda ingin menarik data dari model lain untuk memperbarui entri Anda, Anda harus menjalankan loop for
sean.hudson
31

Pertimbangkan untuk menggunakan yang django-bulk-updateditemukan di sini di GitHub .

Install: pip install django-bulk-update

Implement: (kode diambil langsung dari file proyek 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

Pembaruan: Seperti yang ditunjukkan Marc di komentar, ini tidak cocok untuk memperbarui ribuan baris sekaligus. Padahal itu cocok untuk batch yang lebih kecil, 10 hingga 100-an. Ukuran kumpulan yang tepat untuk Anda bergantung pada CPU Anda dan kompleksitas kueri. Alat ini lebih seperti gerobak dorong daripada dump truck.

nu everest
sumber
16
Saya mencoba django-bulk-update, dan saya pribadi tidak menggunakannya. Apa yang dilakukan secara internal adalah membuat pernyataan SQL tunggal yang terlihat seperti ini: UPDATE "tabel" SET "bidang" = KASUS "id" KAPAN% s KEMUDIAN% s KAPAN% s KEMUDIAN% s [...] WHERE id in ( % s,% s, [...]) ;. Ini agak baik untuk beberapa baris (ketika pembaru massal tidak diperlukan), tetapi dengan 10.000, kueri sangat rumit, sehingga postgres menghabiskan lebih banyak waktu dengan CPU pada 100% memahami kueri, daripada menghemat waktu menulis ke disk .
Marc Garcia
1
@MarcGarcia poin bagus. Saya menemukan banyak pengembang menggunakan perpustakaan eksternal tanpa mengetahui dampaknya
Dejell
3
@MarcGarcia Saya tidak setuju bahwa pembaruan massal tidak berharga dan hanya benar-benar diperlukan ketika ribuan pembaruan diperlukan. Menggunakannya untuk mengerjakan 10.000 baris sekaligus tidak disarankan karena alasan yang Anda sebutkan, tetapi menggunakannya untuk memperbarui 50 baris sekaligus jauh lebih efisien daripada memukul db dengan 50 permintaan pembaruan terpisah.
nu everest
3
Solusi terbaik yang saya temukan adalah: a) gunakan @ transaction.atomic decorator, yang meningkatkan kinerja dengan menggunakan satu transaksi, atau b) membuat penyisipan massal dalam tabel sementara, dan kemudian UPDATE dari tabel sementara ke yang asli.
Marc Garcia
1
Saya tahu ini adalah utas lama, tetapi sebenarnya KASUS / DIMANA bukan satu-satunya cara. Untuk PostgreSQL ada pendekatan lain, tetapi mereka adalah DB spesifik misalnya stackoverflow.com/a/18799497 Namun saya tidak yakin apakah ini mungkin dalam ANSI SQL
Ilian Iliev
21

Versi Django 2.2 sekarang memiliki bulk_updatemetode ( catatan rilis ).

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

Contoh:

# 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
sumber
8

Jika Anda ingin menetapkan nilai yang sama pada kumpulan baris , Anda dapat menggunakan metode pembaruan () yang dikombinasikan dengan istilah kueri apa pun untuk memperbarui semua baris dalam satu permintaan:

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

Jika Anda ingin memperbarui koleksi baris dengan nilai yang berbeda tergantung pada beberapa kondisi, Anda dapat mengumpulkan pembaruan sesuai dengan nilai. Katakanlah Anda memiliki 1000 baris di mana Anda ingin mengatur kolom ke salah satu nilai X, maka Anda bisa menyiapkan batch sebelumnya dan kemudian hanya menjalankan pembaruan-kueri X (masing-masing pada dasarnya memiliki bentuk contoh pertama di atas) + SELECT awal -pertanyaan.

Jika setiap baris membutuhkan nilai unik, tidak ada cara untuk menghindari satu permintaan per pembaruan. Mungkin melihat ke arsitektur lain seperti CQRS / Sumber acara jika Anda membutuhkan kinerja dalam kasus terakhir ini.

Andreas Bergström
sumber
1

IT mengembalikan jumlah objek yang diperbarui dalam tabel.

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

Anda dapat merujuk tautan ini untuk mendapatkan informasi lebih lanjut tentang pembuatan dan pembaruan massal. Pembaruan massal dan Buat

shivam sharma
sumber