Jak przeprowadzić zbiorczą aktualizację za pomocą Django?

163

Chciałbym zaktualizować tabelę za pomocą Django - coś takiego w surowym SQL:

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

Mój pierwszy wynik jest taki - ale to paskudne, prawda?

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

Czy jest bardziej elegancki sposób?

Thomas Schwärzl
źródło
1
Być może szukasz wkładki zbiorczej. Spójrz na stackoverflow.com/questions/4294088/ ...
Pramod
Nie lubię wstawiać nowych danych - po prostu zaktualizuj istniejące.
Thomas Schwärzl
3
Może z pomocą select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.
Co nie jest paskudne w tym ModelClasspodejściu? Następnie
prześlij

Odpowiedzi:

256

Aktualizacja:

Wersja Django 2.2 ma teraz bulk_update .

Stara odpowiedź:

Zapoznaj się z następującą sekcją dokumentacji django

Aktualizowanie wielu obiektów jednocześnie

Krótko mówiąc, powinieneś być w stanie użyć:

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

Możesz także używać Fobiektów do wykonywania takich czynności, jak zwiększanie liczby wierszy:

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

Zobacz dokumentację .

Należy jednak pamiętać, że:

  • To nie użyje ModelClass.savemetody (więc jeśli masz w sobie jakąś logikę, nie zostanie ona uruchomiona).
  • Żadne sygnały django nie będą emitowane.
  • Nie można wykonać operacji .update()na podzielonym zestawie QuerySet, musi on znajdować się na oryginalnym zestawie QuerySet, więc musisz oprzeć się na metodach .filter()i .exclude().
jb.
źródło
27
Należy również zauważyć, że w konsekwencji nie jest używany save(), DateTimeFieldPola auto_now=True( „modyfikowane” kolumny) nie zostaną zaktualizowane.
Arthur
3
Ale ModelClass.objects.filter(name = 'bar').update(name="foo")nie spełnia celu zbiorczej aktualizacji, jeśli mam różne dane dla różnych identyfikatorów, jak mogę to zrobić bez używania pętli?
Shashank
@shihon Nie jestem pewien, czy dobrze cię rozumiem, ale dodałem przykład do odpowiedzi.
jb.
@Shashank Czy znalazłeś już jakieś rozwiązanie dla swojej sprawy? Mam też ten sam scenariusz.
Sourav Prem
Obiekty F nie mogą być używane do odwoływania się do różnych modeli w metodzie .update ... na przykład nie można ich użyć Entry.objects.all().update(title=F('blog__title')). Dokumenty zawierają małą wzmiankę o tym. Jeśli chcesz pobrać dane z innego modelu, aby zaktualizować swoje wpisy, musisz uruchomić pętlę for
sean.hudson
31

Rozważ użycie django-bulk-updateznalezionego tutaj na GitHub .

Zainstalować: pip install django-bulk-update

Implement: (kod pobrany bezpośrednio z pliku ReadMe projektów)

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

Aktualizacja: Jak podkreśla Marc w komentarzach, nie nadaje się do jednoczesnej aktualizacji tysięcy wierszy. Chociaż nadaje się do mniejszych partii od 10 do 100. Wielkość partii, która jest odpowiednia dla Ciebie, zależy od procesora i złożoności zapytań. To narzędzie bardziej przypomina taczkę niż wywrotkę.

nu everest
źródło
16
Wypróbowałem django-bulk-update i osobiście odradzam korzystanie z niego. To, co robi wewnętrznie, to utworzenie pojedynczej instrukcji SQL, która wygląda następująco: UPDATE "table" SET "field" = CASE "id" GDY% s TO% s GDY% s TO% s [...] WHERE id in ( % s,% s, [...]) ;. Jest to trochę w porządku dla kilku wierszy (gdy zbiorczy aktualizator nie jest potrzebny), ale przy 10000 zapytanie jest tak złożone, że postgres spędza więcej czasu z procesorem w 100% rozumiejąc zapytanie, niż czas zapisywania na dysku .
Marc Garcia,
1
@MarcGarcia dobra uwaga. Zauważyłem, że wielu programistów używa zewnętrznych bibliotek, nie wiedząc o ich wpływie
Dejell
3
@MarcGarcia Nie zgadzam się, że zbiorcza aktualizacja nie jest wartościowa i jest naprawdę potrzebna tylko wtedy, gdy konieczne są tysiące aktualizacji. Użycie go do zrobienia 10000 wierszy na raz nie jest zalecane z powodów, o których wspomniałeś, ale użycie go do aktualizacji 50 wierszy na raz jest znacznie bardziej wydajne niż uderzenie w bazę danych z 50 oddzielnymi żądaniami aktualizacji.
nu everest
3
Najlepsze rozwiązania jakie znalazłem to: a) use @ transaction.atomic decorator, która poprawia wydajność poprzez użycie pojedynczej transakcji, lub b) wykonaj zbiorcze wstawianie w tabeli tymczasowej, a następnie AKTUALIZUJ z tabeli tymczasowej do oryginalnej.
Marc Garcia
1
Wiem, że to stary wątek, ale tak naprawdę CASE / WHERE nie jest jedynym sposobem. Dla PostgreSQL są inne podejścia, ale są one specyficzne dla DB, np. Stackoverflow.com/a/18799497 Jednak nie jestem pewien, czy jest to możliwe w ANSI SQL
Ilian Iliev
21

Wersja Django 2.2 ma teraz bulk_updatemetodę ( uwagi do wydania ).

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

Przykład:

# 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
źródło
1
Rzeczywiście, jest on wymieniony w informacjach o wydaniu 2.2
Benoit Blanchon
8

Jeśli chcesz ustawić tę samą wartość w zbiorze wierszy , możesz użyć metody update () w połączeniu z dowolnym terminem zapytania, aby zaktualizować wszystkie wiersze w jednym zapytaniu:

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

Jeśli chcesz zaktualizować kolekcję wierszy z różnymi wartościami w zależności od określonego warunku, w najlepszym przypadku możesz grupować aktualizacje według wartości. Załóżmy, że masz 1000 wierszy, w których chcesz ustawić kolumnę na jedną z wartości X, wtedy możesz wcześniej przygotować partie, a następnie uruchomić tylko X zapytań aktualizujących (każdy zasadniczo ma postać pierwszego przykładu powyżej) + początkowy SELECT -pytanie.

Jeśli każdy wiersz wymaga unikalnej wartości, nie ma możliwości uniknięcia jednego zapytania na aktualizację. Być może przyjrzyj się innym architekturom, takim jak CQRS / Event sourcing, jeśli potrzebujesz wydajności w tym drugim przypadku.

Andreas Bergström
źródło
1

IT zwraca liczbę obiektów aktualizowanych w tabeli.

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

Możesz odesłać ten link, aby uzyskać więcej informacji na temat zbiorczej aktualizacji i tworzenia. Zbiorcza aktualizacja i tworzenie

shivam sharma
źródło