So erhalten Sie Primärschlüssel von Objekten, die mit django mass_create erstellt wurden


74

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?


Ich würde auch gerne wissen, wie die Leute damit umgehen. Ich nehme an, Sie müssten so etwas wie die Tabelle sperren, mass_create ausführen, alle neuen Datensätze abfragen und dann die Tabelle entsperren? Aus den Dokumenten geht ziemlich klar hervor, dass mass_create die auto_increment-Schlüssel nicht zurückgibt. Der einzige Weg, dies zu umgehen, ist eine verschlungene Umgehung. Ich nehme an, die andere Methode wäre, eine andere Tabelle zu haben, mit der Sie die verwendeten Primärschlüssel der Reihe nach verfolgen. Sie weisen also vorher einen Block von IDs zu und führen dann den Befehl lose_create aus, und Sie sollten die erwarteten Primärschlüssel kennen. Ich bin mit keiner der beiden Ideen
zufrieden

2
Es scheint eine Anstrengung zu geben, dies in django dev code.djangoproject.com/ticket/19527
DanH

1
Oh ja! Es scheint, dass mein ~ 4 Jahre alter Vorschlag gerade in die Aktie Django 1.10 eingeschmolzen ist, damit wir alle genießen können. :-) Funktioniert momentan wohl nur für Postgres.
Tuttle

Es ist jetzt mit Django 1.10 und PostgreSQl möglich: docs.djangoproject.com/de/dev/ref/models/querysets/#bulk-create
Maxime R.

hoffentlich gibt es auch eine Unterstützung für MySQL
Roel

Antworten:


65

2016

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


9
Willkommen in der Zukunft
Trinh Hoang Nhu

1
traurig, dass ich ein MySQL-Benutzer bin
Roel

4
Was ist, wenn in MySQL? Haben die von mass_create erstellten Einträge einen ID-Wert in der Datenbank?
Mohammed Shareef C

1
@MohammedShareefC Es wird ein Primärschlüssel in der Datenbank abgerufen, aber die von der bulk_createMethode 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 .
Yushin Washio

Wenn Sie in Datenbanken, die dies unterstützen (alle außer PostgreSQL <9.5 und Oracle), den Parameter ignore_conflicts auf True setzen, wird die Datenbank ignoriert, Fehler beim Einfügen von Zeilen zu ignorieren, bei denen Einschränkungen fehlschlagen, z. B. doppelte eindeutige Werte. Durch Aktivieren dieses Parameters wird das Festlegen des Primärschlüssels für jede Modellinstanz deaktiviert (sofern die Datenbank dies normalerweise unterstützt).
Eugene

30

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.


18
Ich denke, das ist der Punkt der Frage oder zumindest, wie ich sie interpretieren würde, dh: Mit welchen Techniken umgehen die Leute diese Einschränkung bulk_create, um die erstellten IDs zuverlässig abzurufen?
DanH

3
Es gibt eine offene PR, um Unterstützung für die Rückgabe von IDs von mass_create hier hinzuzufügen: github.com/django/django/pull/5166 Insbesondere unterstützt Postgres die Rückgabe von IDs, sodass es eine Möglichkeit gibt, IDs sofort durch eine unformatierte SQL-Operation zurückzugewinnen.
Gordon

26

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


2
Weißt du, ich habe bereits ein date_createdFeld, also könnte dies funktionieren, obwohl es nur ein minimaler Aufwand ist, eines hinzuzufügen. Meine einzige Sorge ist, dass mehrere Abfragen gleichzeitig bulk_createdie created_atDatenbank treffen könnten. Ich nehme an, ich muss vor und nach der Abfrage einen Sperrmechanismus implementieren .
DanH

Ja, atomare Transaktionen könnten verwendet werden, um sicherzustellen, dass Rennbedingungen vermieden werden.
Karthikr

In Bezug auf den ersten Ansatz gibt values_list ('id', flat = True) in Django 1.10 ein Abfrageset zurück, das nach dem Aufruf von mass_create ausgewertet zu werden scheint. Das Einschließen von category_ids in list (), um eine Datenbankabfrage zu erzwingen, hilft.
George

Schrecklich, ich denke sogarselect max(id) is better
Deathangel908

1
@ Deathangel908 Tu es nicht 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.
Patrick

12

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_refdie Sie mit einem eindeutigen Wert füllen, und fügen Sie sie für jede Zeile ein. bulk_refFragen 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)

16
Es wird nicht empfohlen, Ihrem Modell zusätzliche Felder hinzuzufügen, um Abfrageprobleme zu umgehen.
Max

1
Während dies zutrifft, sollten Masseneinsätze sowieso als Optimierung betrachtet werden, die notwendigerweise das Design beeinträchtigen kann. Es gibt eine Spannung zwischen "nicht schnell genug" und "kein perfektes Design", um hier ausgeglichen zu werden. Bis der Django PR 5166 eingesetzt wird, ist dies wahrscheinlich ein vernünftiger Kompromiss für Teams, die die Optimierung eines Bulk-Einsatzes benötigen.
Scott A

Wenn die Massenerstellung in der Anwendung mehrmals zu unterschiedlichen Zeiten aufgerufen wird, müssen wir jedes Mal mass_ref aktualisieren, für das wir eine statis-Variable ref benötigen
varun


1
@DanH scheint eine vernünftige Wahl zu sein, um Abfragen zu vermeiden, und das Hinzufügen eines zusätzlichen Feldes für diesen Zweck könnte tatsächlich sehr hilfreich sein.
Varun

1

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


0

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_createaus 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:

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

  2. Überschreiben / schreiben Sie Ihre eigene Bulk-Insert-Lösung.


0

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.



0

Der von @Or Duan vorgeschlagene Ansatz funktioniert für PostgreSQL bei Verwendung bulk_createmit ignore_conflicts=False. Wenn ignore_conflicts=Truefestgelegt, erhalten Sie die Werte für die AutoField(normalerweise ID) in den zurückgegebenen Objekten nicht.


0
# 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)

-7

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

1
Die Frage war, ob es mit mass_create möglich ist, die Primärschlüssel zurückzubekommen. bulk_create()setzt nicht die Primärschlüssel für die Objekte, die es erstellt!
Kissgyorgy

In dem Objekt, das Sie ausgedruckt haben, fehlt der Primärschlüssel.
Frost

1
Dies würde jetzt allerdings in Postgres funktionieren, ziemlich sicher.
TankorSmash
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.