วิธีการ 'อัปเดตจำนวนมาก' กับ Django ได้อย่างไร


163

ฉันต้องการอัปเดตตารางด้วย Django - อย่างนี้ใน SQL ดิบ:

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

ผลลัพธ์แรกของฉันคืออะไรแบบนี้ - แต่มันก็น่ารังเกียจใช่มั้ย

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

มีวิธีที่สง่างามกว่านี้ไหม?


1
คุณอาจกำลังมองหาชุดแทรก ลองดูที่stackoverflow.com/questions/4294088/…
Pramod

ฉันไม่ชอบที่จะแทรกข้อมูลใหม่ - เพียงแค่อัปเดตที่มีอยู่
Thomas Schwärzl

3
อาจด้วยความช่วยเหลือของ select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.

สิ่งที่ไม่น่ารังเกียจเกี่ยวกับModelClassวิธีการคืออะไร? จากนั้นฟีดไปที่ Django ในฐานะ: stackoverflow.com/questions/16853649/…
Ciro Santilli 郝海东冠状病病六四事件法轮功

คำตอบ:


256

ปรับปรุง:

Django 2.2 รุ่นตอนนี้มีbulk_update

คำตอบเก่า:

อ้างถึงส่วนเอกสารประกอบ django ต่อไปนี้

การอัปเดตวัตถุหลายรายการพร้อมกัน

ในระยะสั้นคุณควรจะสามารถใช้:

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

คุณยังสามารถใช้Fวัตถุเพื่อทำสิ่งต่าง ๆ เช่นการเพิ่มแถว:

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

ดูเอกสาร

อย่างไรก็ตามโปรดทราบว่า:

  • นี่จะไม่ใช้ModelClass.saveวิธีการ (ดังนั้นหากคุณมีตรรกะบางอย่างอยู่ภายในมันจะไม่ถูกเรียกใช้)
  • จะไม่มีการส่งสัญญาณ django
  • คุณไม่สามารถดำเนินการ.update()กับ QuerySet ที่หั่นเป็นชิ้น ๆ นั้นจะต้องอยู่ใน QuerySet ดั้งเดิมดังนั้นคุณจะต้องพึ่งพา.filter()และ.exclude()วิธีการต่างๆ

27
ยังทราบว่าเป็นผลมาจากการไม่ได้ใช้save(), DateTimeFieldเขตข้อมูลที่มีauto_now=True( "ปรับเปลี่ยน" คอลัมน์) จะไม่ได้รับการปรับปรุง
Arthur

3
แต่ModelClass.objects.filter(name = 'bar').update(name="foo")ไม่ได้ทำตามวัตถุประสงค์ของการอัปเดตจำนวนมากหากฉันมีข้อมูลที่แตกต่างกันสำหรับรหัสที่แตกต่างกันฉันจะทำอย่างนั้นได้อย่างไรโดยไม่ต้องใช้ลูป
Shashank

@shihon ฉันไม่แน่ใจว่าฉันทำให้คุณถูกต้อง แต่ฉันเพิ่มตัวอย่างในคำตอบ
jb

@Shashank คุณพบวิธีแก้ปัญหาสำหรับกรณีของคุณหรือยัง ฉันยังมีสถานการณ์เดียวกัน
Sourav Prem

วัตถุ F ไม่สามารถนำมาใช้เพื่อการอ้างอิงรูปแบบที่แตกต่างกันในวิธีการ .update ... Entry.objects.all().update(title=F('blog__title'))ตัวอย่างเช่นคุณไม่สามารถใช้ เอกสารมีการกล่าวถึงเรื่องนี้เล็กน้อย หากคุณต้องการดึงข้อมูลจากแบบจำลองอื่นเพื่ออัปเดตรายการของคุณคุณจะต้องเรียกใช้ for for loop
sean.hudson

31

พิจารณาใช้django-bulk-updateพบว่าที่นี่ใน GitHub

ติดตั้ง: pip install django-bulk-update

ดำเนินการ: (รหัสที่นำมาโดยตรงจากโครงการไฟล์ 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

อัปเดต:ตามที่ Marc ชี้ให้เห็นในความคิดเห็นสิ่งนี้ไม่เหมาะสำหรับการอัปเดตหลายพันแถวพร้อมกัน แม้ว่ามันจะเหมาะสำหรับแบตช์ขนาดเล็ก 10 ถึง 100 ขนาดของชุดงานที่เหมาะกับคุณขึ้นอยู่กับ CPU และความซับซ้อนของแบบสอบถาม เครื่องมือนี้เป็นเหมือนรถเข็นล้อมากกว่ารถดั๊ม


16
ฉันลอง django-bulk-update แล้วและฉันไม่แนะนำให้ใช้ สิ่งที่เกิดขึ้นภายในคือการสร้างคำสั่ง SQL เดี่ยวซึ่งมีลักษณะเช่นนี้: อัปเดต "ตาราง" ฟิลด์ "ชุด" = กรณี "id" เมื่อใดที่% s แล้ว% s เมื่อใด% s จากนั้น ... [WHERE id ใน ( % s,% s, [... ]); นี่เป็นสิ่งที่ถูกต้องสำหรับแถวไม่กี่แถว (เมื่อไม่ต้องการตัวอัปเดตจำนวนมาก) แต่ด้วย 10,000 แบบสอบถามจึงซับซ้อนดังนั้น postgres ใช้เวลามากขึ้นกับ CPU ที่ 100% เข้าใจแบบสอบถามมากกว่าเวลาที่บันทึกการเขียนลงดิสก์ .
Marc Garcia

1
@ MarcGarcia จุดที่ดี ฉันพบว่านักพัฒนาซอฟต์แวร์จำนวนมากใช้ห้องสมุดภายนอกโดยไม่ทราบผลกระทบของมัน
Dejell

3
@MarcGarcia ฉันไม่เห็นด้วยว่าการอัปเดตจำนวนมากไม่จำเป็นต้องมีและจำเป็นจริงๆเมื่อจำเป็นต้องอัปเดตหลายพันรายการ การใช้เพื่อทำ 10,000 แถวพร้อมกันนั้นไม่แนะนำสำหรับเหตุผลที่คุณกล่าวถึง แต่การใช้เพื่อปรับปรุง 50 แถวพร้อมกันนั้นมีประสิทธิภาพมากกว่าการกดปุ่ม db ด้วย 50 คำร้องขอการอัพเดตแยกกัน
nu everest

3
ทางออกที่ดีที่สุดที่ฉันพบคือ:) ใช้ @ transaction.atomic decorator ซึ่งปรับปรุงประสิทธิภาพโดยใช้ธุรกรรมเดียวหรือ b) สร้างการแทรกจำนวนมากในตารางชั่วคราวแล้วอัปเดตจากตารางชั่วคราวไปยังต้นฉบับ
Marc Garcia

1
ฉันรู้ว่านี่เป็นเธรดเก่า แต่จริงๆแล้ว CASE / WHERE ไม่ใช่วิธีเดียว สำหรับ PostgreSQL มีวิธีการอื่น ๆ แต่เป็น DB เฉพาะเช่นstackoverflow.com/a/18799497อย่างไรก็ตามฉันไม่แน่ใจว่าสิ่งนี้เป็นไปได้ใน ANSI SQL
Ilian Iliev

21

รุ่น Django 2.2 ตอนนี้มีbulk_updateวิธีการ ( บันทึกประจำรุ่น )

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

ตัวอย่าง:

# 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

หากคุณต้องการตั้งค่าเดียวกันในกลุ่มของแถวคุณสามารถใช้วิธีการ update () รวมกับคำสืบค้นใด ๆ เพื่อปรับปรุงแถวทั้งหมดในแบบสอบถามเดียว:

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

หากคุณต้องการอัปเดตคอลเลกชันของแถวที่มีค่าต่างกันขึ้นอยู่กับเงื่อนไขบางประการคุณสามารถทำการอัปเดตตามค่าที่เหมาะสม สมมติว่าคุณมี 1,000 แถวที่คุณต้องการตั้งค่าคอลัมน์ให้เป็นหนึ่งในค่า X จากนั้นคุณสามารถเตรียมแบทช์ไว้ล่วงหน้าและจากนั้นเรียกใช้ X update-query -query

หากทุกแถวต้องการค่าที่ไม่ซ้ำกันจะไม่มีวิธีหลีกเลี่ยงหนึ่งแบบสอบถามต่อการปรับปรุง อาจมองเข้าไปในสถาปัตยกรรมอื่น ๆ เช่น CQRS / การจัดหากิจกรรมหากคุณต้องการประสิทธิภาพในกรณีหลังนี้


1

IT ส่งคืนจำนวนวัตถุที่ถูกอัพเดตในตาราง

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

คุณสามารถอ้างอิงลิงค์นี้เพื่อรับข้อมูลเพิ่มเติมเกี่ยวกับการอัปเดตและสร้างเป็นกลุ่ม อัปเดตและสร้างเป็นกลุ่ม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.