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+ ?
Réponses
Trop de publicités?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.
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.
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.
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)
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.
- Réponses précédentes
- Plus de réponses