ORM-fähige INSERT-, UPDATE- und DELETE-Anweisungen

Über dieses Dokument

Dieser Abschnitt verwendet ORM-Zuordnungen, die zuerst im SQLAlchemy Unified Tutorial veranschaulicht wurden, im Abschnitt Deklarieren von zugeordneten Klassen sowie Vererbungszuordnungen, die im Abschnitt Abbilden von Vererbungshierarchien von Klassen gezeigt werden.

ORM-Einrichtung für diese Seite anzeigen.

Die Methode Session.execute() kann neben der Verarbeitung von ORM-fähigen Select-Objekten auch ORM-fähige Insert-, Update- und Delete-Objekte verarbeiten, und zwar auf verschiedene Weise, die jeweils verwendet werden, um viele Datenbankzeilen auf einmal einzufügen, zu aktualisieren oder zu löschen. Es gibt auch dialektspezifische Unterstützung für ORM-fähige „Upserts“, d. h. INSERT-Anweisungen, die automatisch UPDATE für bereits vorhandene Zeilen verwenden.

Die folgende Tabelle fasst die in diesem Dokument besprochenen Aufrufformen zusammen

ORM-Anwendungsfall

Verwendeter DML-Konstrukt

Daten werden über ... übergeben

Unterstützt RETURNING?

Unterstützt Mehrfachzuordnungen?

ORM Bulk INSERT Statements

insert()

Liste von Wörterbüchern an Session.execute.params

ja

ja

ORM Massen-INSERT mit SQL-Ausdrücken

insert()

Session.execute.params mit Insert.values()

ja

ja

ORM Massen-INSERT mit SQL-Ausdrücken pro Zeile

insert()

Liste von Wörterbüchern an Insert.values()

ja

nein

ORM „upsert“-Anweisungen

insert()

Liste von Wörterbüchern an Insert.values()

ja

nein

ORM Bulk UPDATE nach Primärschlüssel

update()

Liste von Wörterbüchern an Session.execute.params

nein

ja

ORM UPDATE und DELETE mit benutzerdefinierten WHERE-Kriterien

update(), delete()

Schlüsselwörter an Update.values()

ja

teilweise, mit manuellen Schritten

ORM Bulk INSERT Statements

Ein insert()-Konstrukt kann in Bezug auf eine ORM-Klasse konstruiert und an die Methode Session.execute() übergeben werden. Eine Liste von Parameterwörterbüchern, die an den Parameter Session.execute.params gesendet werden, getrennt vom Insert-Objekt selbst, löst den **Bulk INSERT-Modus** für die Anweisung aus, was im Wesentlichen bedeutet, dass die Operation für viele Zeilen so weit wie möglich optimiert wird.

>>> from sqlalchemy import insert
>>> session.execute(
...     insert(User),
...     [
...         {"name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"name": "sandy", "fullname": "Sandy Cheeks"},
...         {"name": "patrick", "fullname": "Patrick Star"},
...         {"name": "squidward", "fullname": "Squidward Tentacles"},
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs"},
...     ],
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] [('spongebob', 'Spongebob Squarepants'), ('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star'), ('squidward', 'Squidward Tentacles'), ('ehkrabs', 'Eugene H. Krabs')]
<...>

Die Parameterwörterbücher enthalten Schlüssel/Wert-Paare, die ORM-zugeordneten Attributen entsprechen können, die mit zugeordneten Column- oder mapped_column()-Deklarationen sowie mit Composite-Deklarationen übereinstimmen. Die Schlüssel müssen dem **Namen des ORM-zugeordneten Attributs** und **nicht** dem tatsächlichen Datenbankspaltennamen entsprechen, falls diese beiden Namen unterschiedlich sind.

Geändert in Version 2.0: Das Übergeben eines Insert-Konstrukts an die Methode Session.execute() löst nun einen „Bulk-Insert“ aus, der die gleiche Funktionalität wie die Legacy-Methode Session.bulk_insert_mappings() verwendet. Dies ist eine Verhaltensänderung im Vergleich zur 1.x-Serie, bei der Insert im Core-zentrierten Sinne interpretiert wurde und Spaltennamen für Werteschlüssel verwendet wurden; ORM-Attributschlüssel werden jetzt akzeptiert. Core-ähnliche Funktionalität ist verfügbar, indem die Ausführungsoption {"dml_strategy": "raw"} an den Parameter Session.execution_options von Session.execute() übergeben wird.

Neue Objekte mit RETURNING abrufen

Die ORM-Bulk-Insert-Funktion unterstützt INSERT..RETURNING für ausgewählte Backends, was ein Result-Objekt zurückgeben kann, das einzelne Spalten sowie vollständig konstruierte ORM-Objekte für die neu generierten Datensätze liefert. INSERT..RETURNING erfordert die Verwendung eines Backends, das die SQL RETURNING-Syntax unterstützt, sowie die Unterstützung für executemany mit RETURNING; diese Funktion ist mit allen SQLAlchemy-inkludierten Backends verfügbar, mit Ausnahme von MySQL (MariaDB ist enthalten).

Als Beispiel können wir die gleiche Anweisung wie zuvor ausführen und die Verwendung der Methode UpdateBase.returning() hinzufügen, wobei die vollständige User-Entität als das übergeben wird, was wir zurückgeben möchten. Session.scalars() wird verwendet, um die Iteration von User-Objekten zu ermöglichen.

>>> users = session.scalars(
...     insert(User).returning(User),
...     [
...         {"name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"name": "sandy", "fullname": "Sandy Cheeks"},
...         {"name": "patrick", "fullname": "Patrick Star"},
...         {"name": "squidward", "fullname": "Squidward Tentacles"},
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs"},
...     ],
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?) RETURNING id, name, fullname, species [...] ('spongebob', 'Spongebob Squarepants', 'sandy', 'Sandy Cheeks', 'patrick', 'Patrick Star', 'squidward', 'Squidward Tentacles', 'ehkrabs', 'Eugene H. Krabs')
>>> print(users.all()) [User(name='spongebob', fullname='Spongebob Squarepants'), User(name='sandy', fullname='Sandy Cheeks'), User(name='patrick', fullname='Patrick Star'), User(name='squidward', fullname='Squidward Tentacles'), User(name='ehkrabs', fullname='Eugene H. Krabs')]

Im obigen Beispiel nimmt die gerenderte SQL-Anweisung die Form an, die vom insertmanyvalues-Feature verwendet wird, wie vom SQLite-Backend angefordert, bei dem einzelne Parameterwörterbücher in eine einzige INSERT-Anweisung eingefügt werden, damit RETURNING verwendet werden kann.

Geändert in Version 2.0: Die ORM- Session interpretiert nun RETURNING-Klauseln von Insert-, Update- und sogar Delete-Konstrukten in einem ORM-Kontext, was bedeutet, dass eine Mischung aus Spaltenausdrücken und ORM-zugeordneten Entitäten an die Methode Insert.returning() übergeben werden kann, die dann auf die Weise geliefert werden, wie ORM-Ergebnisse von Konstrukten wie Select geliefert werden, einschließlich der Tatsache, dass zugeordnete Entitäten im Ergebnis als ORM-zugeordnete Objekte geliefert werden. Eingeschränkte Unterstützung für ORM-Loader-Optionen wie load_only() und selectinload() ist ebenfalls vorhanden.

RETURNING-Datensätze mit Eingabedatenreihenfolge korrelieren

Bei der Verwendung von Bulk INSERT mit RETURNING ist zu beachten, dass die meisten Datenbank-Backends keine formelle Garantie für die Reihenfolge geben, in der die Datensätze aus RETURNING zurückgegeben werden, einschließlich der Tatsache, dass keine Garantie besteht, dass ihre Reihenfolge der der Eingabedatensätze entspricht. Für Anwendungen, die sicherstellen müssen, dass RETURNING-Datensätze mit Eingabedaten korreliert werden können, kann der zusätzliche Parameter Insert.returning.sort_by_parameter_order angegeben werden, der je nach Backend spezielle INSERT-Formen verwendet, die ein Token beibehalten, das zur Neuanordnung der zurückgegebenen Zeilen verwendet wird, oder in einigen Fällen, wie im folgenden Beispiel mit dem SQLite-Backend, wird die Operation Zeile für Zeile eingefügt.

>>> data = [
...     {"name": "pearl", "fullname": "Pearl Krabs"},
...     {"name": "plankton", "fullname": "Plankton"},
...     {"name": "gary", "fullname": "Gary"},
... ]
>>> user_ids = session.scalars(
...     insert(User).returning(User.id, sort_by_parameter_order=True), data
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id [... (insertmanyvalues) 1/3 (ordered; batch not supported)] ('pearl', 'Pearl Krabs') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id [insertmanyvalues 2/3 (ordered; batch not supported)] ('plankton', 'Plankton') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id [insertmanyvalues 3/3 (ordered; batch not supported)] ('gary', 'Gary')
>>> for user_id, input_record in zip(user_ids, data): ... input_record["id"] = user_id >>> print(data) [{'name': 'pearl', 'fullname': 'Pearl Krabs', 'id': 6}, {'name': 'plankton', 'fullname': 'Plankton', 'id': 7}, {'name': 'gary', 'fullname': 'Gary', 'id': 8}]

Neu in Version 2.0.10: Hinzugefügt wurde Insert.returning.sort_by_parameter_order, der innerhalb der insertmanyvalues-Architektur implementiert ist.

Siehe auch

RETURNING-Zeilen zu Parametersätzen korrelieren - Hintergrund zu Ansätzen, um die Korrespondenz zwischen Eingabedaten und Ergebniszeilen ohne signifikanten Leistungsverlust zu gewährleisten

Heterogene Parameterwörterbücher verwenden

Die ORM-Bulk-Insert-Funktion unterstützt Listen von Parameterwörterbüchern, die „heterogen“ sind, was im Grunde bedeutet, „einzelne Wörterbücher können unterschiedliche Schlüssel haben“. Wenn diese Bedingung erkannt wird, zerlegt die ORM die Parameterwörterbücher in Gruppen, die jedem Schlüsselsatz entsprechen, und stapelt sie entsprechend in separate INSERT-Anweisungen.

>>> users = session.scalars(
...     insert(User).returning(User),
...     [
...         {
...             "name": "spongebob",
...             "fullname": "Spongebob Squarepants",
...             "species": "Sea Sponge",
...         },
...         {"name": "sandy", "fullname": "Sandy Cheeks", "species": "Squirrel"},
...         {"name": "patrick", "species": "Starfish"},
...         {
...             "name": "squidward",
...             "fullname": "Squidward Tentacles",
...             "species": "Squid",
...         },
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs", "species": "Crab"},
...     ],
... )
INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?), (?, ?, ?) RETURNING id, name, fullname, species [... (insertmanyvalues) 1/1 (unordered)] ('spongebob', 'Spongebob Squarepants', 'Sea Sponge', 'sandy', 'Sandy Cheeks', 'Squirrel') INSERT INTO user_account (name, species) VALUES (?, ?) RETURNING id, name, fullname, species [...] ('patrick', 'Starfish') INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?), (?, ?, ?) RETURNING id, name, fullname, species [... (insertmanyvalues) 1/1 (unordered)] ('squidward', 'Squidward Tentacles', 'Squid', 'ehkrabs', 'Eugene H. Krabs', 'Crab')

Im obigen Beispiel führten die fünf übergebenen Parameterwörterbücher zu drei INSERT-Anweisungen, die entlang der spezifischen Schlüsselsätze in jedem Wörterbuch gruppiert wurden, während die Zeilenreihenfolge beibehalten wurde, d. h. ("name", "fullname", "species"), ("name", "species"), ("name","fullname", "species").

NULL-Werte in ORM Massen-INSERT-Anweisungen senden

Die ORM-Bulk-Insert-Funktion greift auf ein Verhalten zurück, das auch im Legacy-„Bulk“-Insert-Verhalten vorhanden ist, sowie in der ORM-Einheit der Arbeit insgesamt, nämlich dass Zeilen, die NULL-Werte enthalten, mit einer Anweisung eingefügt werden, die diese Spalten nicht referenziert; die Begründung dafür ist, dass Backends und Schemata mit serverseitigen INSERT-Standardwerten, die auf die Anwesenheit eines NULL-Werts gegenüber dem Fehlen eines Wertes reagieren, einen serverseitigen Wert wie erwartet erzeugen. Dieses Standardverhalten hat zur Folge, dass die Masseneinfüge-Stapel in mehr Stapel mit weniger Zeilen aufgeteilt werden.

>>> session.execute(
...     insert(User),
...     [
...         {
...             "name": "name_a",
...             "fullname": "Employee A",
...             "species": "Squid",
...         },
...         {
...             "name": "name_b",
...             "fullname": "Employee B",
...             "species": "Squirrel",
...         },
...         {
...             "name": "name_c",
...             "fullname": "Employee C",
...             "species": None,
...         },
...         {
...             "name": "name_d",
...             "fullname": "Employee D",
...             "species": "Bluefish",
...         },
...     ],
... )
INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?) [...] [('name_a', 'Employee A', 'Squid'), ('name_b', 'Employee B', 'Squirrel')] INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] ('name_c', 'Employee C') INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?) [...] ('name_d', 'Employee D', 'Bluefish') ...

Oben wird der Massen-INSERT von vier Zeilen in drei separate Anweisungen aufgeteilt, wobei die zweite Anweisung neu formatiert wurde, um die NULL-Spalte für das einzelne Parameterwörterbuch nicht zu referenzieren, das einen None-Wert enthält. Dieses Standardverhalten kann unerwünscht sein, wenn viele Zeilen im Datensatz zufällige NULL-Werte enthalten, da es dazu führt, dass die „executemany“-Operation in eine größere Anzahl kleinerer Operationen aufgeteilt wird; insbesondere bei der Nutzung von insertmanyvalues zur Reduzierung der Gesamtzahl der Anweisungen kann dies größere Auswirkungen auf die Leistung haben.

Um die Verarbeitung von None-Werten in den Parametern in separate Stapel zu deaktivieren, übergeben Sie die Ausführungsoption render_nulls=True; dies führt dazu, dass alle Parameterwörterbücher als gleichwertig behandelt werden, unter der Annahme, dass jedes Wörterbuch denselben Satz von Schlüsseln hat.

>>> session.execute(
...     insert(User).execution_options(render_nulls=True),
...     [
...         {
...             "name": "name_a",
...             "fullname": "Employee A",
...             "species": "Squid",
...         },
...         {
...             "name": "name_b",
...             "fullname": "Employee B",
...             "species": "Squirrel",
...         },
...         {
...             "name": "name_c",
...             "fullname": "Employee C",
...             "species": None,
...         },
...         {
...             "name": "name_d",
...             "fullname": "Employee D",
...             "species": "Bluefish",
...         },
...     ],
... )
INSERT INTO user_account (name, fullname, species) VALUES (?, ?, ?) [...] [('name_a', 'Employee A', 'Squid'), ('name_b', 'Employee B', 'Squirrel'), ('name_c', 'Employee C', None), ('name_d', 'Employee D', 'Bluefish')] ...

Oben werden alle Parameterwörterbücher in einem einzigen INSERT-Stapel übergeben, einschließlich des None-Werts im dritten Parameterwörterbuch.

Neu in Version 2.0.23: Hinzugefügt wurde die Ausführungsoption render_nulls, die das Verhalten des Legacy-Parameters Session.bulk_insert_mappings.render_nulls widerspiegelt.

Massen-INSERT für Joined Table Inheritance

ORM-Massen-INSERT baut auf dem internen System auf, das vom traditionellen Unit of Work-System verwendet wird, um INSERT-Anweisungen zu generieren. Das bedeutet, dass für eine ORM-Entität, die auf mehrere Tabellen abgebildet ist, typischerweise eine, die mit Joined Table Inheritance abgebildet ist, der Massen-INSERT-Vorgang eine INSERT-Anweisung für jede von der Abbildung dargestellte Tabelle generiert und die serverseitig generierten Primärschlüsselwerte korrekt an die Zeilen weitergibt, die von ihnen abhängen. Das RETURNING-Feature wird hier ebenfalls unterstützt, wobei die ORM Result-Objekte für jede ausgeführte INSERT-Anweisung erhält und diese dann „horizontal zusammenfügt“, sodass die zurückgegebenen Zeilen Werte für alle eingefügten Spalten enthalten.

>>> managers = session.scalars(
...     insert(Manager).returning(Manager),
...     [
...         {"name": "sandy", "manager_name": "Sandy Cheeks"},
...         {"name": "ehkrabs", "manager_name": "Eugene H. Krabs"},
...     ],
... )
INSERT INTO employee (name, type) VALUES (?, ?) RETURNING id, name, type [... (insertmanyvalues) 1/2 (ordered; batch not supported)] ('sandy', 'manager') INSERT INTO employee (name, type) VALUES (?, ?) RETURNING id, name, type [insertmanyvalues 2/2 (ordered; batch not supported)] ('ehkrabs', 'manager') INSERT INTO manager (id, manager_name) VALUES (?, ?), (?, ?) RETURNING id, manager_name, id AS id__1 [... (insertmanyvalues) 1/1 (ordered)] (1, 'Sandy Cheeks', 2, 'Eugene H. Krabs')

Tipp

Massen-INSERT von Joined-Inheritance-Zuordnungen erfordert, dass die ORM intern den Parameter Insert.returning.sort_by_parameter_order verwendet, um Primärschlüsselwerte aus RETURNING-Zeilen von der Basistabelle mit den Parametersätzen korrelieren zu können, die zum Einfügen in die „Unter“-Tabelle verwendet werden. Deshalb wird beim SQLite-Backend, das oben veranschaulicht wird, transparent auf nicht gestapelte Anweisungen zurückgegriffen. Hintergrundinformationen zu diesem Feature finden Sie unter RETURNING-Zeilen zu Parametersätzen korrelieren.

ORM Massen-INSERT mit SQL-Ausdrücken

Die ORM-Bulk-Insert-Funktion unterstützt die Hinzufügung eines festen Satzes von Parametern, die SQL-Ausdrücke enthalten können, die auf jede Zielzeile angewendet werden. Um dies zu erreichen, kombinieren Sie die Verwendung der Methode Insert.values(), die ein Wörterbuch von Parametern übergibt, die auf alle Zeilen angewendet werden, mit der üblichen Bulk-Aufrufform durch Einbeziehung einer Liste von Parameterwörterbüchern, die einzelne Zeilenwerte enthalten, wenn Session.execute() aufgerufen wird.

Als Beispiel, gegeben eine ORM-Zuordnung, die eine „Timestamp“-Spalte enthält

import datetime


class LogRecord(Base):
    __tablename__ = "log_record"
    id: Mapped[int] = mapped_column(primary_key=True)
    message: Mapped[str]
    code: Mapped[str]
    timestamp: Mapped[datetime.datetime]

Wenn wir eine Reihe von LogRecord-Elementen einfügen möchten, jeweils mit einem eindeutigen message-Feld, aber wir möchten die SQL-Funktion now() auf alle Zeilen anwenden, können wir timestamp in Insert.values() übergeben und dann die zusätzlichen Datensätze im „Bulk“-Modus übergeben.

>>> from sqlalchemy import func
>>> log_record_result = session.scalars(
...     insert(LogRecord).values(code="SQLA", timestamp=func.now()).returning(LogRecord),
...     [
...         {"message": "log message #1"},
...         {"message": "log message #2"},
...         {"message": "log message #3"},
...         {"message": "log message #4"},
...     ],
... )
INSERT INTO log_record (message, code, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP), (?, ?, CURRENT_TIMESTAMP), (?, ?, CURRENT_TIMESTAMP), (?, ?, CURRENT_TIMESTAMP) RETURNING id, message, code, timestamp [... (insertmanyvalues) 1/1 (unordered)] ('log message #1', 'SQLA', 'log message #2', 'SQLA', 'log message #3', 'SQLA', 'log message #4', 'SQLA')
>>> print(log_record_result.all()) [LogRecord('log message #1', 'SQLA', datetime.datetime(...)), LogRecord('log message #2', 'SQLA', datetime.datetime(...)), LogRecord('log message #3', 'SQLA', datetime.datetime(...)), LogRecord('log message #4', 'SQLA', datetime.datetime(...))]

ORM Massen-INSERT mit SQL-Ausdrücken pro Zeile

Die Methode Insert.values() selbst nimmt eine Liste von Parameterwörterbüchern direkt auf. Wenn das Insert-Konstrukt auf diese Weise verwendet wird, ohne eine Liste von Parameterwörterbüchern an den Parameter Session.execute.params zu übergeben, wird der Bulk ORM-Insert-Modus nicht verwendet, und stattdessen wird die INSERT-Anweisung genau wie angegeben gerendert und genau einmal ausgeführt. Dieser Betriebsmodus kann sowohl für den Fall der Übergabe von SQL-Ausdrücken pro Zeile nützlich sein als auch bei der Verwendung von „Upsert“-Anweisungen mit der ORM, die später in diesem Kapitel unter ORM „Upsert“-Anweisungen dokumentiert sind.

Ein konstruiertes Beispiel für eine INSERT-Anweisung, die SQL-Ausdrücke pro Zeile einbettet und auch Insert.returning() in dieser Form demonstriert, ist unten aufgeführt.

>>> from sqlalchemy import select
>>> address_result = session.scalars(
...     insert(Address)
...     .values(
...         [
...             {
...                 "user_id": select(User.id).where(User.name == "sandy"),
...                 "email_address": "sandy@company.com",
...             },
...             {
...                 "user_id": select(User.id).where(User.name == "spongebob"),
...                 "email_address": "spongebob@company.com",
...             },
...             {
...                 "user_id": select(User.id).where(User.name == "patrick"),
...                 "email_address": "patrick@company.com",
...             },
...         ]
...     )
...     .returning(Address),
... )
INSERT INTO address (user_id, email_address) VALUES ((SELECT user_account.id FROM user_account WHERE user_account.name = ?), ?), ((SELECT user_account.id FROM user_account WHERE user_account.name = ?), ?), ((SELECT user_account.id FROM user_account WHERE user_account.name = ?), ?) RETURNING id, user_id, email_address [...] ('sandy', 'sandy@company.com', 'spongebob', 'spongebob@company.com', 'patrick', 'patrick@company.com')
>>> print(address_result.all()) [Address(email_address='sandy@company.com'), Address(email_address='spongebob@company.com'), Address(email_address='patrick@company.com')]

Da der Bulk ORM Insert-Modus oben nicht verwendet wird, sind die folgenden Funktionen nicht vorhanden:

  • Joined Table Inheritance oder andere Mehrfachzuordnungen werden nicht unterstützt, da dies mehrere INSERT-Anweisungen erfordern würde.

  • Heterogene Parametersätze werden nicht unterstützt – jedes Element im VALUES-Satz muss dieselben Spalten haben.

  • Core-Level-Skalierungsoptimierungen wie die Stapelung durch insertmanyvalues sind nicht verfügbar; Anweisungen müssen sicherstellen, dass die Gesamtzahl der Parameter die vom zugrunde liegenden Datenbanksystem auferlegten Grenzen nicht überschreitet.

Aus diesen Gründen wird im Allgemeinen nicht empfohlen, mehrere Parametersätze mit Insert.values() mit ORM INSERT-Anweisungen zu verwenden, es sei denn, es gibt einen klaren Grund dafür, nämlich entweder dass „Upsert“ verwendet wird oder dass SQL-Ausdrücke pro Zeile in jedem Parametersatz eingebettet werden müssen.

Legacy Session Bulk INSERT Methoden

Die Session enthält Legacy-Methoden für die Ausführung von „Bulk“-INSERT- und UPDATE-Anweisungen. Diese Methoden teilen sich Implementierungen mit den SQLAlchemy 2.0-Versionen dieser Features, die unter ORM Bulk INSERT Statements und ORM Bulk UPDATE nach Primärschlüssel beschrieben sind, es fehlen jedoch viele Funktionen, insbesondere RETURNING-Unterstützung sowie Unterstützung für Session-Synchronisation.

Code, der Session.bulk_insert_mappings() verwendet, kann beispielsweise wie folgt portiert werden, beginnend mit diesem Mappings-Beispiel

session.bulk_insert_mappings(User, [{"name": "u1"}, {"name": "u2"}, {"name": "u3"}])

Das Obige wird mit der neuen API ausgedrückt als

from sqlalchemy import insert

session.execute(insert(User), [{"name": "u1"}, {"name": "u2"}, {"name": "u3"}])

ORM „Upsert“-Anweisungen

Ausgewählte Backends mit SQLAlchemy können dialektspezifische Insert-Konstrukte enthalten, die zusätzlich die Fähigkeit haben, „Upserts“ durchzuführen, oder INSERTs, bei denen eine vorhandene Zeile im Parametersatz in eine Annäherung einer UPDATE-Anweisung umgewandelt wird. Unter „vorhandene Zeile“ können Zeilen gemeint sein, die denselben Primärschlüsselwert teilen, oder sich auf andere indizierte Spalten innerhalb der Zeile beziehen, die als eindeutig gelten; dies hängt von den Fähigkeiten des verwendeten Backends ab.

Die in SQLAlchemy enthaltenen Dialekte, die dialektspezifische „Upsert“-API-Funktionen enthalten, sind:

Benutzer sollten die obigen Abschnitte für Hintergrundinformationen zur korrekten Konstruktion dieser Objekte prüfen; insbesondere muss sich die „Upsert“-Methode typischerweise auf die ursprüngliche Anweisung beziehen, daher wird die Anweisung normalerweise in zwei separaten Schritten konstruiert.

Drittanbieter-Backends wie die unter Externe Dialekte genannten können ebenfalls ähnliche Konstrukte aufweisen.

Obwohl SQLAlchemy noch keinen Backend-agnostischen Upsert-Konstrukt hat, sind die oben genannten Insert-Varianten dennoch ORM-kompatibel, da sie auf die gleiche Weise wie der Insert-Konstrukt selbst verwendet werden können, wie unter ORM Massen-INSERT mit SQL-Ausdrücken pro Zeile dokumentiert, d. h. durch Einbettung der gewünschten Zeilen zum Einfügen in die Methode Insert.values(). Im folgenden Beispiel wird die SQLite-Funktion insert() verwendet, um ein Insert-Konstrukt zu generieren, das die Unterstützung für „ON CONFLICT DO UPDATE“ enthält. Die Anweisung wird dann an Session.execute() übergeben, wo sie normal ausgeführt wird, mit der zusätzlichen Besonderheit, dass die an Insert.values() übergebenen Parameterwörterbücher als ORM-zuordnete Attributschlüssel und nicht als Spaltennamen interpretiert werden.

>>> from sqlalchemy.dialects.sqlite import insert as sqlite_upsert
>>> stmt = sqlite_upsert(User).values(
...     [
...         {"name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"name": "sandy", "fullname": "Sandy Cheeks"},
...         {"name": "patrick", "fullname": "Patrick Star"},
...         {"name": "squidward", "fullname": "Squidward Tentacles"},
...         {"name": "ehkrabs", "fullname": "Eugene H. Krabs"},
...     ]
... )
>>> stmt = stmt.on_conflict_do_update(
...     index_elements=[User.name], set_=dict(fullname=stmt.excluded.fullname)
... )
>>> session.execute(stmt)
INSERT INTO user_account (name, fullname) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?) ON CONFLICT (name) DO UPDATE SET fullname = excluded.fullname [...] ('spongebob', 'Spongebob Squarepants', 'sandy', 'Sandy Cheeks', 'patrick', 'Patrick Star', 'squidward', 'Squidward Tentacles', 'ehkrabs', 'Eugene H. Krabs')
<...>

RETURNING mit Upsert-Anweisungen verwenden

Aus Sicht der SQLAlchemy ORM sehen Upsert-Anweisungen wie reguläre Insert-Konstrukte aus, einschließlich der Tatsache, dass Insert.returning() mit Upsert-Anweisungen auf die gleiche Weise funktioniert, wie unter ORM Massen-INSERT mit SQL-Ausdrücken pro Zeile gezeigt, sodass beliebige Spaltenausdrücke oder relevante ORM-Entitätsklassen übergeben werden können. Fortfahrend aus dem Beispiel im vorherigen Abschnitt.

>>> result = session.scalars(
...     stmt.returning(User), execution_options={"populate_existing": True}
... )
INSERT INTO user_account (name, fullname) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?) ON CONFLICT (name) DO UPDATE SET fullname = excluded.fullname RETURNING id, name, fullname, species [...] ('spongebob', 'Spongebob Squarepants', 'sandy', 'Sandy Cheeks', 'patrick', 'Patrick Star', 'squidward', 'Squidward Tentacles', 'ehkrabs', 'Eugene H. Krabs')
>>> print(result.all()) [User(name='spongebob', fullname='Spongebob Squarepants'), User(name='sandy', fullname='Sandy Cheeks'), User(name='patrick', fullname='Patrick Star'), User(name='squidward', fullname='Squidward Tentacles'), User(name='ehkrabs', fullname='Eugene H. Krabs')]

Das obige Beispiel verwendet RETURNING, um ORM-Objekte für jede Zeile zurückzugeben, die von der Anweisung eingefügt oder aktualisiert wurde. Das Beispiel fügt auch die Verwendung der Ausführungsoption Populate Existing hinzu. Diese Option gibt an, dass User-Objekte, die bereits in der Session für Zeilen vorhanden sind, die bereits existieren, mit den Daten aus der neuen Zeile aktualisiert werden sollen. Für eine reine Insert-Anweisung ist diese Option nicht relevant, da jede erzeugte Zeile eine brandneue Primärschlüssel-Identität hat. Wenn die Insert jedoch auch „Upsert“-Optionen enthält, kann sie auch Ergebnisse von Zeilen liefern, die bereits existieren und daher bereits eine Primärschlüssel-Identität im Identity Map des Session-Objekts repräsentieren.

ORM Bulk UPDATE nach Primärschlüssel

Der Update-Konstrukt kann mit Session.execute() ähnlich verwendet werden wie die Insert-Anweisung, wie unter ORM Bulk INSERT Statements beschrieben, wobei eine Liste von vielen Parameter-Dictionaries übergeben wird, wobei jedes Dictionary eine einzelne Zeile repräsentiert, die einem einzelnen Primärschlüsselwert entspricht. Diese Verwendung sollte nicht mit einer gebräuchlicheren Art der Verwendung von Update-Anweisungen mit dem ORM verwechselt werden, bei der eine explizite WHERE-Klausel verwendet wird, was unter ORM UPDATE und DELETE mit benutzerdefinierten WHERE-Kriterien dokumentiert ist.

Für die „Bulk“-Version von UPDATE wird ein update()-Konstrukt im Hinblick auf eine ORM-Klasse erstellt und an die Methode Session.execute() übergeben; das resultierende Update-Objekt sollte keine Werte und typischerweise keine WHERE-Kriterien enthalten, d.h. die Methode Update.values() wird nicht verwendet, und Update.where() wird normalerweise nicht verwendet, kann aber in dem ungewöhnlichen Fall verwendet werden, dass zusätzliche Filterkriterien hinzugefügt werden.

Das Übergeben des Update-Konstrukts zusammen mit einer Liste von Parameter-Dictionaries, die jeweils einen vollständigen Primärschlüsselwert enthalten, ruft den Modus Bulk UPDATE nach Primärschlüssel für die Anweisung auf, generiert die entsprechenden WHERE-Kriterien, um jede Zeile anhand des Primärschlüssels abzugleichen, und verwendet executemany, um jeden Parametersatz gegen die UPDATE-Anweisung auszuführen.

>>> from sqlalchemy import update
>>> session.execute(
...     update(User),
...     [
...         {"id": 1, "fullname": "Spongebob Squarepants"},
...         {"id": 3, "fullname": "Patrick Star"},
...         {"id": 5, "fullname": "Eugene H. Krabs"},
...     ],
... )
UPDATE user_account SET fullname=? WHERE user_account.id = ? [...] [('Spongebob Squarepants', 1), ('Patrick Star', 3), ('Eugene H. Krabs', 5)]
<...>

Beachten Sie, dass jedes Parameter-Dictionary einen vollständigen Primärschlüssel für jeden Datensatz enthalten muss, andernfalls wird ein Fehler ausgelöst.

Ähnlich wie beim Bulk-INSERT-Feature werden auch hier heterogene Parameterlisten unterstützt, wobei die Parameter in Unterstapel von UPDATE-Läufen gruppiert werden.

Geändert in Version 2.0.11: Zusätzliche WHERE-Kriterien können mit ORM Bulk UPDATE nach Primärschlüssel kombiniert werden, indem die Methode Update.where() verwendet wird, um zusätzliche Kriterien hinzuzufügen. Diese Kriterien sind jedoch immer zusätzlich zu den bereits vorhandenen WHERE-Kriterien, die die Primärschlüsselwerte enthalten.

Das RETURNING-Feature ist bei Verwendung von „Bulk UPDATE nach Primärschlüssel“ nicht verfügbar; die Liste mehrerer Parameter-Dictionaries verwendet zwangsläufig DBAPI executemany, das in seiner üblichen Form normalerweise keine Ergebniszeilen unterstützt.

Geändert in Version 2.0: Das Übergeben eines Update-Konstrukts an die Methode Session.execute() zusammen mit einer Liste von Parameter-Dictionaries löst jetzt ein „Bulk Update“ aus, das die gleiche Funktionalität wie die Legacy-Methode Session.bulk_update_mappings() verwendet. Dies ist eine Verhaltensänderung im Vergleich zur 1.x-Serie, in der das Update nur mit expliziten WHERE-Kriterien und Inline-VALUES unterstützt wurde.

Deaktivieren von Bulk ORM Update nach Primärschlüssel für eine UPDATE-Anweisung mit mehreren Parametersätzen

Das Feature ORM Bulk Update nach Primärschlüssel, das eine UPDATE-Anweisung pro Datensatz ausführt, die WHERE-Kriterien für jeden Primärschlüsselwert enthält, wird automatisch verwendet, wenn

  1. die angegebene UPDATE-Anweisung sich gegen eine ORM-Entität richtet

  2. die Session zum Ausführen der Anweisung verwendet wird und keine Core Connection

  3. Die übergebenen Parameter sind eine Liste von Dictionaries.

Um eine UPDATE-Anweisung ohne „ORM Bulk Update nach Primärschlüssel“ aufzurufen, rufen Sie die Anweisung direkt gegen die Connection auf, indem Sie die Methode Session.connection() verwenden, um die aktuelle Connection für die Transaktion zu erhalten

>>> from sqlalchemy import bindparam
>>> session.connection().execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )
UPDATE user_account SET fullname=? WHERE user_account.name = ? [...] [('Spongebob Squarepants', 'spongebob'), ('Patrick Star', 'patrick')]
<...>

Bulk UPDATE nach Primärschlüssel für Joined Table Inheritance

ORM Bulk Update verhält sich ähnlich wie ORM Bulk Insert bei der Verwendung von Mappings mit Joined Table Inheritance; wie unter Bulk INSERT für Joined Table Inheritance beschrieben, gibt die Bulk-UPDATE-Operation eine UPDATE-Anweisung für jede im Mapping dargestellte Tabelle aus, für die die übergebenen Parameter Werte zum Aktualisieren enthalten (nicht betroffene Tabellen werden übersprungen).

Beispiel

>>> session.execute(
...     update(Manager),
...     [
...         {
...             "id": 1,
...             "name": "scheeks",
...             "manager_name": "Sandy Cheeks, President",
...         },
...         {
...             "id": 2,
...             "name": "eugene",
...             "manager_name": "Eugene H. Krabs, VP Marketing",
...         },
...     ],
... )
UPDATE employee SET name=? WHERE employee.id = ? [...] [('scheeks', 1), ('eugene', 2)] UPDATE manager SET manager_name=? WHERE manager.id = ? [...] [('Sandy Cheeks, President', 1), ('Eugene H. Krabs, VP Marketing', 2)]
<...>

Legacy Session Bulk UPDATE-Methoden

Wie unter Legacy Session Bulk INSERT Methods erläutert, ist die Methode Session.bulk_update_mappings() von Session die Legacy-Form von Bulk Update, die das ORM intern verwendet, wenn eine update()-Anweisung mit Primärschlüsselparametern interpretiert wird; bei der Verwendung der Legacy-Version sind jedoch Funktionen wie die Unterstützung für Sitzungssynchronisation nicht enthalten.

Das Beispiel unten

session.bulk_update_mappings(
    User,
    [
        {"id": 1, "name": "scheeks", "manager_name": "Sandy Cheeks, President"},
        {"id": 2, "name": "eugene", "manager_name": "Eugene H. Krabs, VP Marketing"},
    ],
)

Ist mit der neuen API wie folgt ausgedrückt:

from sqlalchemy import update

session.execute(
    update(User),
    [
        {"id": 1, "name": "scheeks", "manager_name": "Sandy Cheeks, President"},
        {"id": 2, "name": "eugene", "manager_name": "Eugene H. Krabs, VP Marketing"},
    ],
)

ORM UPDATE und DELETE mit benutzerdefinierten WHERE-Kriterien

Die Konstrukte Update und Delete, wenn sie mit benutzerdefinierten WHERE-Kriterien (d.h. unter Verwendung der Methoden Update.where() und Delete.where()) konstruiert werden, können in einem ORM-Kontext aufgerufen werden, indem sie an Session.execute() übergeben werden, ohne den Parameter Session.execute.params zu verwenden. Für Update sollten die zu aktualisierenden Werte über Update.values() übergeben werden.

Diese Nutzungsweise unterscheidet sich von dem zuvor unter ORM Bulk UPDATE nach Primärschlüssel beschriebenen Feature dadurch, dass das ORM die angegebene WHERE-Klausel so verwendet, wie sie ist, anstatt die WHERE-Klausel auf Primärschlüssel festzulegen. Das bedeutet, dass die einzelne UPDATE- oder DELETE-Anweisung auf einmal viele Zeilen beeinflussen kann.

Als Beispiel wird unten ein UPDATE ausgegeben, das das Feld „fullname“ mehrerer Zeilen beeinflusst

>>> from sqlalchemy import update
>>> stmt = (
...     update(User)
...     .where(User.name.in_(["squidward", "sandy"]))
...     .values(fullname="Name starts with S")
... )
>>> session.execute(stmt)
UPDATE user_account SET fullname=? WHERE user_account.name IN (?, ?) [...] ('Name starts with S', 'squidward', 'sandy')
<...>

Für ein DELETE, ein Beispiel zum Löschen von Zeilen basierend auf Kriterien

>>> from sqlalchemy import delete
>>> stmt = delete(User).where(User.name.in_(["squidward", "sandy"]))
>>> session.execute(stmt)
DELETE FROM user_account WHERE user_account.name IN (?, ?) [...] ('squidward', 'sandy')
<...>

Warnung

Bitte lesen Sie den folgenden Abschnitt Wichtige Hinweise und Vorbehalte für ORM-aktiviertes Update und Delete für wichtige Anmerkungen dazu, wie sich die Funktionalität von ORM-aktivierten UPDATE und DELETE von der ORM Unit of Work-Funktionen unterscheidet, wie z. B. die Verwendung der Methode Session.delete() zum Löschen einzelner Objekte.

Wichtige Hinweise und Vorbehalte für ORM-aktiviertes Update und Delete

Die ORM-aktivierten UPDATE- und DELETE-Features umgehen die ORM Unit of Work-Automatisierung zugunsten der Fähigkeit, eine einzige UPDATE- oder DELETE-Anweisung auszugeben, die mehrere Zeilen gleichzeitig ohne Komplexität abgleicht.

  • Die Operationen bieten keine In-Python-Kaskadierung von Beziehungen – es wird davon ausgegangen, dass ON UPDATE CASCADE und/oder ON DELETE CASCADE für alle Fremdschlüsselreferenzen konfiguriert sind, die dies erfordern, andernfalls kann die Datenbank eine Integritätsverletzung ausgeben, wenn Fremdschlüsselreferenzen erzwungen werden. Siehe die Hinweise unter Verwendung von Fremdschlüssel ON DELETE Kaskadierung mit ORM-Beziehungen für einige Beispiele.

  • Nach dem UPDATE oder DELETE können abhängige Objekte in der Session, die von einer ON UPDATE CASCADE- oder ON DELETE CASCADE-Regelung auf verwandten Tabellen betroffen waren, insbesondere Objekte, die sich auf jetzt gelöschte Zeilen beziehen, diese Objekte immer noch referenzieren. Dieses Problem wird behoben, sobald die Session abgelaufen ist, was normalerweise bei Session.commit() geschieht oder durch die Verwendung von Session.expire_all() erzwungen werden kann.

  • ORM-aktivierte UPDATEs und DELETEs verarbeiten Joined Table Inheritance nicht automatisch. Siehe den Abschnitt UPDATE/DELETE mit benutzerdefinierten WHERE-Kriterien für Joined Table Inheritance für Hinweise zur Arbeit mit Joined-Inheritance-Mappings.

  • Die WHERE-Kriterien, die erforderlich sind, um die polymorphe Identität auf bestimmte Unterklassen für Single-Table-Inheritance-Mappings zu beschränken, sind automatisch enthalten. Dies gilt nur für einen Unterklassen-Mapper, der keine eigene Tabelle hat.

  • Die Option with_loader_criteria() wird unterstützt von ORM Update- und Delete-Operationen; Kriterien hier werden zu denen der ausgegebenen UPDATE- oder DELETE-Anweisung hinzugefügt und auch während des „Synchronisations“-Prozesses berücksichtigt.

  • Um ORM-aktivierte UPDATE- und DELETE-Operationen mit Event-Handlern abzufangen, verwenden Sie das Event SessionEvents.do_orm_execute().

Auswahl einer Synchronisationsstrategie

Bei der Verwendung von update() oder delete() in Verbindung mit ORM-aktivierter Ausführung über Session.execute() ist zusätzliche ORM-spezifische Funktionalität vorhanden, die den von der Anweisung geänderten Zustand mit dem der Objekte, die sich derzeit im Identity Map der Session befinden, synchronisiert. Mit „synchronisieren“ meinen wir, dass aktualisierte Attribute mit dem neuen Wert aufgefrischt werden, oder zumindest abgelaufen werden, damit sie bei nächstem Zugriff mit ihrem neuen Wert wieder aufgefüllt werden, und gelöschte Objekte in den Zustand gelöscht versetzt werden.

Diese Synchronisation ist als „Synchronisationsstrategie“ steuerbar, die als String-ORM-Ausführungsoption übergeben wird, typischerweise über das Dictionary Session.execute.execution_options

>>> from sqlalchemy import update
>>> stmt = (
...     update(User).where(User.name == "squidward").values(fullname="Squidward Tentacles")
... )
>>> session.execute(stmt, execution_options={"synchronize_session": False})
UPDATE user_account SET fullname=? WHERE user_account.name = ? [...] ('Squidward Tentacles', 'squidward')
<...>

Die Ausführungsoption kann auch mit der Anweisung selbst über die Methode Executable.execution_options() gebündelt werden

>>> from sqlalchemy import update
>>> stmt = (
...     update(User)
...     .where(User.name == "squidward")
...     .values(fullname="Squidward Tentacles")
...     .execution_options(synchronize_session=False)
... )
>>> session.execute(stmt)
UPDATE user_account SET fullname=? WHERE user_account.name = ? [...] ('Squidward Tentacles', 'squidward')
<...>

Die folgenden Werte für synchronize_session werden unterstützt

  • 'auto' – Dies ist der Standard. Die Strategie 'fetch' wird auf Backends verwendet, die RETURNING unterstützen, was alle SQLAlchemy-nativen Treiber außer MySQL umfasst. Wenn RETURNING nicht unterstützt wird, wird stattdessen die Strategie 'evaluate' verwendet.

  • 'fetch' – Ruft die Primärschlüssel-Identität von betroffenen Zeilen ab, entweder durch eine SELECT-Abfrage vor dem UPDATE oder DELETE oder durch die Verwendung von RETURNING, wenn die Datenbank dies unterstützt, sodass In-Memory-Objekte, die von der Operation betroffen sind, mit neuen Werten aktualisiert (Updates) oder aus der Session entfernt (Deletes) werden können. Diese Synchronisationsstrategie kann auch verwendet werden, wenn das angegebene update()- oder delete()-Konstrukt explizit Entitäten oder Spalten unter Verwendung von UpdateBase.returning() angibt.

    Geändert in Version 2.0: Explizites UpdateBase.returning() kann mit der Synchronisationsstrategie 'fetch' kombiniert werden, wenn ORM-aktivierte UPDATE und DELETE mit WHERE-Kriterien verwendet werden. Die tatsächliche Anweisung enthält die Vereinigung der Spalten zwischen dem, was die 'fetch'-Strategie erfordert, und dem, was angefordert wurde.

  • 'evaluate' – Dies weist an, die WHERE-Kriterien in der UPDATE- oder DELETE-Anweisung in Python auszuwerten, um passende Objekte innerhalb der Session zu finden. Dieser Ansatz fügt der Operation keine zusätzlichen SQL-Roundtrips hinzu und kann in Ermangelung von RETURNING-Unterstützung effizienter sein. Bei UPDATE- oder DELETE-Anweisungen mit komplexen Kriterien kann die 'evaluate'-Strategie den Ausdruck nicht in Python auswerten und löst einen Fehler aus. Wenn dies geschieht, verwenden Sie stattdessen die 'fetch'-Strategie für die Operation.

    Tipp

    Wenn ein SQL-Ausdruck benutzerdefinierte Operatoren unter Verwendung von Operators.op() oder des custom_op-Features verwendet, kann der Parameter Operators.op.python_impl verwendet werden, um eine Python-Funktion anzugeben, die von der "evaluate"-Synchronisationsstrategie verwendet wird.

    Neu in Version 2.0.

    Warnung

    Die "evaluate"-Strategie sollte vermieden werden, wenn eine UPDATE-Operation auf einer Session ausgeführt werden soll, die viele abgelaufene Objekte enthält, da sie zwangsläufig Objekte neu laden muss, um sie gegen die angegebenen WHERE-Kriterien zu testen, was für jedes einzelne eine SELECT-Abfrage auslöst. In diesem Fall und insbesondere wenn das Backend RETURNING unterstützt, sollte die "fetch"-Strategie bevorzugt werden.

  • False – Synchronisieren Sie die Sitzung nicht. Diese Option kann für Backends nützlich sein, die RETURNING nicht unterstützen, wo die "evaluate"-Strategie nicht verwendet werden kann. In diesem Fall bleibt der Zustand der Objekte in der Session unverändert und entspricht nicht automatisch der ausgegebenen UPDATE- oder DELETE-Anweisung, wenn solche Objekte, die normalerweise den abgeglichenen Zeilen entsprechen würden, vorhanden sind.

Verwendung von RETURNING mit UPDATE/DELETE und benutzerdefinierten WHERE-Kriterien

Die Methode UpdateBase.returning() ist vollständig kompatibel mit ORM-aktiviertem UPDATE und DELETE mit WHERE-Kriterien. Vollständige ORM-Objekte und/oder Spalten können für RETURNING angegeben werden

>>> from sqlalchemy import update
>>> stmt = (
...     update(User)
...     .where(User.name == "squidward")
...     .values(fullname="Squidward Tentacles")
...     .returning(User)
... )
>>> result = session.scalars(stmt)
UPDATE user_account SET fullname=? WHERE user_account.name = ? RETURNING id, name, fullname, species [...] ('Squidward Tentacles', 'squidward')
>>> print(result.all()) [User(name='squidward', fullname='Squidward Tentacles')]

Die Unterstützung für RETURNING ist auch mit der fetch-Synchronisationsstrategie kompatibel, die ebenfalls RETURNING verwendet. Das ORM organisiert die Spalten in RETURNING entsprechend, damit die Synchronisation so gut wie möglich verläuft und das zurückgegebene Result die angeforderten Entitäten und SQL-Spalten in ihrer angeforderten Reihenfolge enthält.

Neu in Version 2.0: UpdateBase.returning() kann für ORM-aktivierte UPDATE und DELETE verwendet werden, während die vollständige Kompatibilität mit der fetch-Synchronisationsstrategie erhalten bleibt.

UPDATE/DELETE mit benutzerdefinierten WHERE-Kriterien für Joined Table Inheritance

Das Feature UPDATE/DELETE mit WHERE-Kriterien gibt im Gegensatz zum ORM Bulk UPDATE nach Primärschlüssel nur eine einzige UPDATE- oder DELETE-Anweisung pro Aufruf von Session.execute() aus. Das bedeutet, dass bei der Ausführung einer update()- oder delete()-Anweisung gegen ein Multi-Table-Mapping, wie z.B. eine Unterklasse in einem Joined-Table-Inheritance-Mapping, die Anweisung den aktuellen Fähigkeiten des Backends entsprechen muss, was einschließen kann, dass das Backend keine UPDATE- oder DELETE-Anweisung unterstützt, die sich auf mehrere Tabellen bezieht, oder nur eingeschränkte Unterstützung dafür bietet. Das bedeutet, dass für Mappings wie Joined-Inheritance-Unterklassen die ORM-Version des Features UPDATE/DELETE mit WHERE-Kriterien nur begrenzt oder gar nicht verwendet werden kann, je nach Details.

Der einfachste Weg, eine Multi-Row-UPDATE-Anweisung für eine Joined-Table-Unterklasse auszugeben, ist die Bezugnahme auf die Untertabelle allein. Das bedeutet, dass das Update()-Konstrukt nur auf Attribute verweisen sollte, die lokal zur Untertabellentabelle sind, wie im folgenden Beispiel

>>> stmt = (
...     update(Manager)
...     .where(Manager.id == 1)
...     .values(manager_name="Sandy Cheeks, President")
... )
>>> session.execute(stmt)
UPDATE manager SET manager_name=? WHERE manager.id = ? [...] ('Sandy Cheeks, President', 1) <...>

Mit der obigen Form ist eine rudimentäre Möglichkeit, auf die Basistabelle zu verweisen, um Zeilen zu lokalisieren, die auf jedem SQL-Backend funktionieren, die Verwendung einer Subquery

>>> stmt = (
...     update(Manager)
...     .where(
...         Manager.id
...         == select(Employee.id).where(Employee.name == "sandy").scalar_subquery()
...     )
...     .values(manager_name="Sandy Cheeks, President")
... )
>>> session.execute(stmt)
UPDATE manager SET manager_name=? WHERE manager.id = (SELECT employee.id FROM employee WHERE employee.name = ?) RETURNING id [...] ('Sandy Cheeks, President', 'sandy')
<...>

Für Backends, die UPDATE…FROM unterstützen, kann die Subquery stattdessen als zusätzliche einfache WHERE-Kriterien angegeben werden, jedoch müssen die Kriterien zwischen den beiden Tabellen auf irgendeine Weise explizit angegeben werden.

>>> stmt = (
...     update(Manager)
...     .where(Manager.id == Employee.id, Employee.name == "sandy")
...     .values(manager_name="Sandy Cheeks, President")
... )
>>> session.execute(stmt)
UPDATE manager SET manager_name=? FROM employee WHERE manager.id = employee.id AND employee.name = ? [...] ('Sandy Cheeks, President', 'sandy')
<...>

Für ein DELETE wird erwartet, dass Zeilen sowohl in der Basistabelle als auch in der Untertabelle gleichzeitig gelöscht werden. Um viele Zeilen von Joined-Inheritance-Objekten zu löschen, ohne kaskadierende Fremdschlüssel zu verwenden, geben Sie DELETE für jede Tabelle einzeln aus

>>> from sqlalchemy import delete
>>> session.execute(delete(Manager).where(Manager.id == 1))
DELETE FROM manager WHERE manager.id = ? [...] (1,)
<...> >>> session.execute(delete(Employee).where(Employee.id == 1))
DELETE FROM employee WHERE employee.id = ? [...] (1,)
<...>

Insgesamt sollten normale Unit of Work-Prozesse für das Aktualisieren und Löschen von Zeilen für Joined-Inheritance und andere Multi-Table-Mappings bevorzugt werden, es sei denn, es gibt einen Performance-Grund für die Verwendung von benutzerdefinierten WHERE-Kriterien.

Legacy Query-Methoden

Das Feature ORM enabled UPDATE/DELETE mit WHERE war ursprünglich Teil des jetzt-legacy Query-Objekts in den Methoden Query.update() und Query.delete(). Diese Methoden bleiben verfügbar und bieten einen Teil der gleichen Funktionalität wie unter ORM UPDATE und DELETE mit benutzerdefinierten WHERE-Kriterien beschrieben. Der Hauptunterschied besteht darin, dass die Legacy-Methoden keine explizite RETURNING-Unterstützung bieten.