Gibt es eine Möglichkeit, die Primärschlüssel der Elemente abzurufen, die Sie mit der Funktion mass_create in django 1.4+ erstellt haben?
Gibt es eine Möglichkeit, die Primärschlüssel der Elemente abzurufen, die Sie mit der Funktion mass_create in django 1.4+ erstellt haben?
Antworten:
Seit Django 1.10 - es wird jetzt unterstützt (nur auf Postgres) ist hier ein Link zum Dokument .
>>> 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
Aus dem Änderungsprotokoll:
In Django 1.10 geändert: Unterstützung für das Festlegen von Primärschlüsseln für Objekte, die mit mass_create () bei Verwendung von PostgreSQL erstellt wurden, wurde hinzugefügt
bulk_create
Methode zurückgegebene Liste ist dieselbe, die Sie angegeben haben, und die lokalen Objekte (Mitglieder dieser Liste) haben sie nicht festgelegt, wie pyriku in seiner Antwort demonstriert .
Laut Dokumentation können Sie dies nicht tun: https://docs.djangoproject.com/de/dev/ref/models/querysets/#bulk-create
Bulk-Create ist genau dafür: Erstellen Sie viele Objekte auf effiziente Weise und speichern Sie viele Abfragen. Dies bedeutet jedoch, dass die Antwort, die Sie erhalten, unvollständig ist. Wenn Sie tun:
>>> 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]
Das bedeutet nicht, dass Ihre Kategorien kein pk haben, nur dass die Abfrage sie nicht abgerufen hat (wenn der Schlüssel ein ist AutoField
). Wenn Sie die pks aus irgendeinem Grund möchten, müssen Sie die Objekte auf klassische Weise speichern.
bulk_create
, um die erstellten IDs zuverlässig abzurufen?
Zwei Ansätze, die ich mir vorstellen kann:
a) Sie könnten tun
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)
Dies kann etwas teuer sein, wenn das Abfrageset extrem groß ist.
b) Wenn das Modell ein created_at
Feld hat,
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)
Dies hat die Einschränkung, dass ein Feld vorhanden ist, in dem gespeichert wird, wann das Objekt erstellt wurde.
date_created
Feld, also könnte dies funktionieren, obwohl es nur ein minimaler Aufwand ist, eines hinzuzufügen. Meine einzige Sorge ist, dass mehrere Abfragen gleichzeitig bulk_create
die created_at
Datenbank treffen könnten. Ich nehme an, ich muss vor und nach der Abfrage einen Sperrmechanismus implementieren .
select max(id) is better
max(id)
, ich habe es versucht und bin auf Probleme gestoßen . In der MariaDB-Dokumentation wird ausdrücklich angegeben, dass nichts anderes als die Einzigartigkeit der PK angenommen werden soll.
Eigentlich hat mein Kollege die folgende Lösung vorgeschlagen, die jetzt alles so offensichtlich erscheint. Fügen Sie eine neue Spalte mit dem Namen hinzu, bulk_ref
die Sie mit einem eindeutigen Wert füllen, und fügen Sie sie für jede Zeile ein. bulk_ref
Fragen Sie anschließend einfach vorher die Tabelle mit dem Set ab und voila, Ihre eingefügten Datensätze werden abgerufen. z.B:
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)
Ich habe viele Strategien ausprobiert, um diese Einschränkung von MariaDB / MySQL zu umgehen. Die einzige zuverlässige Lösung, die ich am Ende gefunden habe, war das Generieren der Primärschlüssel in der Anwendung. Generieren INT AUTO_INCREMENT
Sie KEINE PK-Felder selbst, dies funktioniert nicht einmal in einer Transaktion mit Isolationsstufe serializable
, da der PK-Zähler in MariaDB nicht durch Transaktionssperren geschützt ist.
Die Lösung besteht darin UUID
, den Modellen eindeutige Felder hinzuzufügen , ihre Werte in der Modellklasse zu generieren und diese dann als Bezeichner zu verwenden. Wenn Sie eine Reihe von Modellen in der Datenbank speichern, erhalten Sie immer noch nicht die tatsächliche PK zurück, aber das ist in Ordnung, da Sie sie in nachfolgenden Abfragen eindeutig mit ihrer UUID identifizieren können.
In der Django-Dokumentation sind derzeit folgende Einschränkungen aufgeführt:
Wenn der Primärschlüssel des Modells ein AutoField ist, wird das Primärschlüsselattribut nicht wie gewohnt abgerufen und festgelegt
save()
.
Aber es gibt gute Nachrichten. Es gab ein paar Tickets, über die bulk_create
aus dem Gedächtnis gesprochen wurde. Das oben aufgeführte Ticket hat höchstwahrscheinlich eine Lösung, die bald implementiert wird, aber es gibt offensichtlich keine Garantie für die Zeit oder ob es jemals gelingen wird.
Es gibt also zwei mögliche Lösungen:
Warten Sie ab, ob dieser Patch die Produktion erreicht. Sie können dabei helfen, indem Sie die angegebene Lösung testen und die Django-Community über Ihre Gedanken / Probleme informieren. https://code.djangoproject.com/attachment/ticket/19527/bulk_create_and_create_schema_django_v1.5.1.patch
Überschreiben / schreiben Sie Ihre eigene Bulk-Insert-Lösung.
Die wahrscheinlich einfachste Problemumgehung besteht darin, Primärschlüssel manuell zuzuweisen. Es hängt vom jeweiligen Fall ab, aber manchmal reicht es aus, mit max (id) +1 aus der Tabelle zu beginnen und jedem Objekt inkrementelle Nummern zuzuweisen. Wenn jedoch mehrere Clients gleichzeitig Datensätze einfügen können, ist möglicherweise eine Sperre erforderlich.
Dies funktioniert nicht auf Lager Django, aber es gibt einen Patch im Django-Bug-Tracker , mit dem mass_create die Primärschlüssel für erstellte Objekte festlegt.
Der von @Or Duan vorgeschlagene Ansatz funktioniert für PostgreSQL bei Verwendung bulk_create
mit ignore_conflicts=False
. Wenn ignore_conflicts=True
festgelegt, erhalten Sie die Werte für die AutoField
(normalerweise ID) in den zurückgegebenen Objekten nicht.
# datatime.py
# my datatime function
def getTimeStamp(needFormat=0, formatMS=True):
if needFormat != 0:
return datetime.datetime.now().strftime(f'%Y-%m-%d %H:%M:%S{r".%f" if formatMS else ""}')
else:
ft = time.time()
return (ft if formatMS else int(ft))
def getTimeStampString():
return str(getTimeStamp()).replace('.', '')
# model
bulk_marker = models.CharField(max_length=32, blank=True, null=True, verbose_name='bulk_marker', help_text='ONLYFOR_bulkCreate')
# views
import .........getTimeStampString
data_list(
Category(title="title1", bulk_marker=getTimeStampString()),
...
)
# bulk_create
Category.objects.bulk_create(data_list)
# Get primary Key id
Category.objects.filter(bulk_marker=bulk_marker).values_list('id', flat=True)
Das sollte funktionieren.
categories = Category.objects.bulk_create([
Category(titel="Python", user=user),
Category(titel="Django", user=user),
Category(titel="HTML5", user=user),
])
>>> categories[0]
[<Category: Python>]
>>> categories[1]
[<Category: Django>]
bulk_create()
setzt nicht die Primärschlüssel für die Objekte, die es erstellt!