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?


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

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().

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ę.


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])


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.


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.