Comment mettre à jour en masse avec Django?

163

J'aimerais mettre à jour une table avec Django - quelque chose comme ça en SQL brut:

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

Mon premier résultat est quelque chose comme ça - mais c'est méchant, n'est-ce pas?

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

Y a-t-il une manière plus élégante?

Thomas Schwärzl
la source
1
Vous recherchez peut-être une insertion par lots. Jetez un œil à stackoverflow.com/questions/4294088/…
Pramod
Je n'aime pas insérer de nouvelles données - il suffit de mettre à jour les données existantes.
Thomas Schwärzl
3
Peut-être avec l'aide de select_for_update? docs.djangoproject.com/en/dev/ref/models/querysets/…
Jure C.
Qu'est-ce qui n'est pas méchant dans l' ModelClassapproche? Puis alimentez Django comme
suit

Réponses:

256

Mettre à jour:

La version Django 2.2 a maintenant un bulk_update .

Ancienne réponse:

Reportez-vous à la section de documentation django suivante

Mettre à jour plusieurs objets à la fois

En bref, vous devriez pouvoir utiliser:

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

Vous pouvez également utiliser des Fobjets pour faire des choses comme l'incrémentation de lignes:

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

Consultez la documentation .

Cependant, notez que:

  • Cela n'utilisera pas de ModelClass.saveméthode (donc si vous avez une logique à l'intérieur, elle ne sera pas déclenchée).
  • Aucun signal django ne sera émis.
  • Vous ne pouvez pas effectuer un .update()sur un QuerySet découpé, il doit être sur un QuerySet d'origine, vous devrez donc vous appuyer sur les méthodes .filter()et .exclude().
jb.
la source
27
Notez également qu'en cas de non utilisation save(), les DateTimeFieldchamps avec auto_now=True(colonnes "modifiées") ne seront pas mis à jour.
Arthur
3
Mais ModelClass.objects.filter(name = 'bar').update(name="foo")ne remplit pas l'objectif de la mise à jour en masse, si j'ai des données différentes pour différents identifiants, comment puis-je le faire sans utiliser de boucle?
Shashank
@shihon Je ne sais pas si je vous ai bien compris, mais j'ai ajouté un exemple à la réponse.
jb.
@Shashank, avez-vous déjà trouvé une solution pour votre cas? J'ai aussi le même scénario.
Sourav Prem
Les objets F ne peuvent pas être utilisés pour référencer différents modèles dans la méthode .update ... par exemple, vous ne pouvez pas utiliser Entry.objects.all().update(title=F('blog__title')). Les documents en font une petite mention. Si vous souhaitez extraire des données d'un autre modèle pour mettre à jour vos entrées, vous devrez exécuter une boucle for
sean.hudson
31

Pensez à utiliser django-bulk-updatefound here sur GitHub .

Installer: pip install django-bulk-update

Implémenter: (code extrait directement du fichier ReadMe des projets)

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

Mise à jour: comme Marc le souligne dans les commentaires, cela ne convient pas pour mettre à jour des milliers de lignes à la fois. Bien qu'il soit adapté aux petits lots de 10 à 100. La taille du lot qui vous convient dépend de votre processeur et de la complexité de vos requêtes. Cet outil ressemble plus à une brouette qu'à un camion à benne basculante.

nu everest
la source
16
J'ai essayé django-bulk-update et je déconseille personnellement de l'utiliser. Ce qu'il fait en interne est de créer une seule instruction SQL qui ressemble à ceci: UPDATE "table" SET "field" = CASE "id" WHEN% s ALORS% s WHEN% s ALORS% s [...] WHERE id in ( % s,% s, [...]) ;. C'est assez bien pour quelques lignes (lorsque le programme de mise à jour en masse n'est pas nécessaire), mais avec 10000, la requête est si complexe que postgres passe plus de temps avec le processeur à 100% à comprendre la requête, que le temps qu'il économise d'écrire sur le disque .
Marc Garcia
1
@MarcGarcia bon point. J'ai trouvé que de nombreux développeurs utilisent des bibliothèques externes sans connaître leur impact
Dejell
3
@MarcGarcia Je ne suis pas d'accord pour dire que la mise à jour en masse n'est pas utile et n'est vraiment nécessaire que lorsque des milliers de mises à jour sont nécessaires. L'utiliser pour faire 10 000 lignes à la fois n'est pas conseillé pour les raisons que vous avez mentionnées, mais l'utiliser pour mettre à jour 50 lignes à la fois est beaucoup plus efficace que de frapper la base de données avec 50 demandes de mise à jour distinctes.
nu everest
3
Les meilleures solutions que j'ai trouvées sont: a) utiliser @ transaction.atomic decorator, qui améliore les performances en utilisant une seule transaction, ou b) faire une insertion en bloc dans une table temporaire, puis une MISE À JOUR de la table temporaire vers la table d'origine.
Marc Garcia
1
Je sais que c'est un vieux fil, mais en fait CASE / WHERE n'est pas le seul moyen. Pour PostgreSQL, il existe d'autres approches, mais elles sont spécifiques à la base de données, par exemple stackoverflow.com/a/18799497 Cependant, je ne suis pas sûr que cela soit possible dans ANSI SQL
Ilian Iliev
21

La version Django 2.2 dispose désormais d'une bulk_updateméthode ( notes de publication ).

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

Exemple:

# 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
la source
8

Si vous souhaitez définir la même valeur sur une collection de lignes , vous pouvez utiliser la méthode update () combinée avec n'importe quel terme de requête pour mettre à jour toutes les lignes dans une requête:

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

Si vous souhaitez mettre à jour une collection de lignes avec des valeurs différentes selon certaines conditions, vous pouvez dans le meilleur des cas regrouper les mises à jour en fonction de valeurs. Supposons que vous ayez 1000 lignes dans lesquelles vous souhaitez définir une colonne sur l'une des valeurs X, puis vous pouvez préparer les lots à l'avance, puis n'exécuter que X requêtes de mise à jour (chacune ayant essentiellement la forme du premier exemple ci-dessus) + le SELECT initial -requete.

Si chaque ligne nécessite une valeur unique, il n'y a aucun moyen d'éviter une requête par mise à jour. Peut-être envisager d'autres architectures comme CQRS / Event sourcing si vous avez besoin de performances dans ce dernier cas.

Andreas Bergström
la source
1

IT renvoie le nombre d'objets mis à jour dans le tableau.

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

Vous pouvez consulter ce lien pour obtenir plus d'informations sur la mise à jour et la création groupées. Mise à jour et création en masse

shivam sharma
la source