96 votes

Comment obtenir les clés primaires des objets créés avec django bulk_create ?

Existe-t-il un moyen d'obtenir les clés primaires des éléments que vous avez créés en utilisant la fonction bulk_create dans django 1.4+ ?

90voto

Or Duan Points 5708

2016

Depuis Django 1.10 - il est désormais pris en charge (sur Postgres uniquement), voici une lien vers le document .

>>> list_of_objects = Entry.objects.bulk_create([
...     Entry(headline="Django 2.0 Released"),
...     Entry(headline="Django 2.1 Announced"),
...     Entry(headline="Breaking: Django is awesome")
... ])
>>> list_of_objects[0].id
1

A partir du journal des modifications :

Modifié dans Django 1.10 : La prise en charge de la définition des clés primaires sur les objets créés à l'aide de bulk_create() lors de l'utilisation de PostgreSQL a été ajoutée.

42voto

pyriku Points 668

Selon la documentation, vous ne pouvez pas le faire : https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create

bulk-create sert à cela : créer un grand nombre d'objets de manière efficace en économisant beaucoup de requêtes. Mais cela signifie que la réponse que vous obtenez est en quelque sorte incomplète. Si vous le faites :

>>> categories = Category.objects.bulk_create([
    Category(titel="Python", user=user),
    Category(titel="Django", user=user),
    Category(titel="HTML5", user=user),
])

>>> [x.pk for x in categories]
[None, None, None]

Cela ne signifie pas que vos catégories n'ont pas de pk, juste que la requête ne les a pas récupérés (si la clé est un AutoField ). Si vous voulez les pks pour une raison quelconque, vous devrez sauvegarder les objets d'une manière classique.

36voto

karthikr Points 36157

Deux approches auxquelles je peux penser :

a) Vous pourriez faire

category_ids = Category.objects.values_list('id', flat=True)
categories = Category.objects.bulk_create([
    Category(title="title1", user=user, created_at=now),
    Category(title="title2", user=user, created_at=now),
    Category(title="title3", user=user, created_at=now),
])
new_categories_ids = Category.objects.exclude(id__in=category_ids).values_list('id', flat=True)

Cela peut s'avérer un peu coûteux si le jeu de requêtes est extrêmement volumineux.

b) Si le modèle a un created_at champ,

now = datetime.datetime.now()
categories = Category.objects.bulk_create([
    Category(title="title1", user=user, created_at=now),
    Category(title="title2", user=user, created_at=now),
    Category(title="title3", user=user, created_at=now),
])

new_cats = Category.objects.filter(created_at >= now).values_list('id', flat=True)

Cette méthode présente l'inconvénient d'avoir un champ qui enregistre la date de création de l'objet.

14voto

DanH Points 719

En fait, mon collègue a suggéré la solution suivante, qui semble si évidente maintenant. Ajouter une nouvelle colonne appelée bulk_ref que vous remplissez avec une valeur unique et que vous insérez pour chaque ligne. Ensuite, il suffit d'interroger la table avec la méthode bulk_ref défini au préalable et voilà, vos enregistrements insérés sont récupérés. ex :

cars = [Car(
    model="Ford",
    color="Blue",
    price="5000",
    bulk_ref=5,
),Car(
    model="Honda",
    color="Silver",
    price="6000",
    bulk_ref=5,
)]
Car.objects.bulk_create(cars)
qs = Car.objects.filter(bulk_ref=5)

4voto

Hoang Dung Pham Points 366

Je vais vous faire partager AUTO_INCREMENT la manipulation dans InnoDB (MySQL) et l'approche pour obtenir la clé primaire lorsque bulk_create (Django)

Según Création en masse de documents If the model’s primary key is an AutoField it does not retrieve and set the primary key attribute, as save() does, unless the database backend supports it (currently PostgreSQL). Nous devons donc trouver la cause du problème dans Django ou MySQL avant de chercher une solution.

En AUTO FIELD dans Django est en fait AUTO_INCREMENT dans MySQL. Il permet de générer une identité unique pour les nouvelles lignes ( réf. )

Vous voulez bulk_create objets (Django) signifie insert multiple rows in a single SQL query . Mais comment récupérer la plus récente clé primaire (PK) générée automatiquement ? Merci à LAST_INSERT_ID . It returns first value automatically generated of the most recently executed INSERT statement...This value cannot be affected by other clients, even if they generate AUTO_INCREMENT values of their own. This behavior ensures that each client can retrieve its own ID without concern for the activity of other clients, and without the need for locks or transactions.

Je vous encourage à lire Gestion de AUTO_INCREMENT dans InnoDB et lire le code de Django django.db.models.query.QuerySet.bulk_create pour savoir pourquoi Django ne le supporte pas encore pour MySQl. C'est intéressant. Revenez ici et commentez votre idée, s'il vous plaît.

Ensuite, je vais vous montrer un exemple de code :

from django.db import connections, models, transaction
from django.db.models import AutoField, sql

def dict_fetch_all(cursor):
    """Return all rows from a cursor as a dict"""
    columns = [col[0] for col in cursor.description]
    return [
        dict(zip(columns, row))
        for row in cursor.fetchall()
    ]

class BulkQueryManager(models.Manager):
    def bulk_create_return_with_id(self, objs, batch_size=2000):
        self._for_write = True
        fields = [f for f in self.model._meta.concrete_fields if not isinstance(f, AutoField)]
        created_objs = []
        with transaction.atomic(using=self.db):
            with connections[self.db].cursor() as cursor:
                for item in [objs[i:i + batch_size] for i in range(0, len(objs), batch_size)]:
                    query = sql.InsertQuery(self.model)
                    query.insert_values(fields, item)
                    for raw_sql, params in query.get_compiler(using=self.db).as_sql():
                        cursor.execute(raw_sql, params)
                    raw = "SELECT * FROM %s WHERE id >= %s ORDER BY id DESC LIMIT %s" % (
                        self.model._meta.db_table, cursor.lastrowid, cursor.rowcount
                    )
                    cursor.execute(raw)
                    created_objs.extend(dict_fetch_all(cursor))

        return created_objs

class BookTab(models.Model):
    name = models.CharField(max_length=128)
    bulk_query_manager = BulkQueryManager()

    class Meta:
        db_table = 'book_tab'

def test():
    x = [BookTab(name="1"), BookTab(name="2")]
    create_books = BookTab.bulk_query_manager.bulk_create_return_with_id(x)
    print(create_books)  # [{'id': 2, 'name': '2'}, {'id': 1, 'name': '1'}]

L'idée est d'utiliser cursor d'exécuter raw insert sql et ensuite récupérer created_records. D'après AUTO_INCREMENT handling in InnoDB il s'assure qu'il n'y aura pas d'enregistrements qui interrompront votre objs de PK cursor.lastrowid - len(objs) + 1 to cursor.lastrowid ( curseur.lastrowid ).

Bonus : il est en cours de production dans mon entreprise. Mais vous devez vous préoccuper de size affect c'est pourquoi Django ne le supporte pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X