Операции INSERT, UPDATE и DELETE с поддержкой ORM

О данном документе

В этом разделе используются отображения ORM, впервые показанные в разделе Унифицированный учебник по SQLAlchemy, показанные в разделе Объявление сопоставленных классов, а также отображения наследования, показанные в разделе Отображение иерархий наследования классов.

View the ORM setup for this page.

Метод Session.execute(), помимо работы с ORM-объектами Select, может также работать с ORM-объектами Insert, Update и Delete, различными способами используемыми для INSERT, UPDATE или DELETE многих строк базы данных одновременно. Кроме того, в диалекте реализована поддержка «апсетов» с поддержкой ORM, которые представляют собой операторы INSERT, автоматически использующие UPDATE для уже существующих строк.

В следующей таблице приведены формы вызова, которые рассматриваются в данном документе:

Пример использования ORM

Используемая конструкция DML

Данные передаются с помощью …

Поддерживаете ли вы возвращение?

Поддержка отображения нескольких таблиц?

ORM Bulk INSERT Statements

insert()

Список словарей для Session.execute.params

yes

yes

ORM Bulk Insert с использованием SQL-выражений

insert()

Session.execute.params с Insert.values()

yes

yes

ORM Bulk Insert с использованием SQL-выражений для каждой строки

insert()

Список словарей для Insert.values()

yes

нет

ORM «upsert» Statements

insert()

Список словарей для Insert.values()

yes

нет

ORM Bulk UPDATE по первичному ключу

update()

Список словарей для Session.execute.params

нет

yes

ORM UPDATE и DELETE с пользовательскими критериями WHERE

update(), delete()

ключевые слова в Update.values()

yes

partial, with manual steps

ORM Bulk INSERT Statements

Конструкция insert() может быть построена в терминах класса ORM и передана методу Session.execute(). Список словарей параметров, передаваемый в параметр Session.execute.params, отдельно от самого объекта Insert, вызовет для оператора режим bulk INSERT, что, по сути, означает, что операция будет максимально оптимизирована для многих строк:

>>> 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"},
...     ],
... )
{execsql}INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] [('spongebob', 'Spongebob Squarepants'), ('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star'),
('squidward', 'Squidward Tentacles'), ('ehkrabs', 'Eugene H. Krabs')]
{stop}<...>

Словари параметров содержат пары ключ/значение, которые могут соответствовать отображенным атрибутам ORM, соответствующим отображенным объявлениям Column или mapped_column(), а также объявлениям composite. Ключи должны соответствовать имени отображенного атрибута ОРМ и не реальному имени столбца базы данных, если эти два имени различаются.

Изменено в версии 2.0: Передача конструкции Insert в метод Session.execute() теперь вызывает «массовую вставку», которая использует ту же функциональность, что и старый метод Session.bulk_insert_mappings(). Это изменение поведения по сравнению с серией 1.x, где Insert интерпретировался в Core-ориентированном стиле, используя имена столбцов для ключей значений; теперь принимаются ключи атрибутов ORM. Функциональность в стиле Core доступна при передаче опции выполнения {"dml_strategy": "raw"} параметру Session.execution_options в методе Session.execute().

Получение новых объектов с помощью RETURNING

Функция массовой вставки ORM поддерживает функцию INSERT..RETURNING для выбранных бэкендов, которая может возвращать объект Result, в котором могут быть как отдельные колонки, так и полностью построенные объекты ORM, соответствующие вновь созданным записям. INSERT..RETURNING требует использования бэкенда, поддерживающего синтаксис SQL RETURNING, а также поддержку executemany с RETURNING; эта возможность доступна для всех бэкендов SQLAlchemy-included, за исключением MySQL (MariaDB включена).

В качестве примера мы можем выполнить тот же оператор, что и раньше, добавив использование метода UpdateBase.returning(), передав полную сущность User в качестве того, что мы хотели бы вернуть. Метод Session.scalars() используется для итерации объектов User:

>>> 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"},
...     ],
... )
{execsql}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')
{stop}>>> 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')]

В приведенном примере визуализированный SQL принимает вид, используемый функцией insertmanyvalues по запросу бэкенда SQLite, где отдельные словари параметров встраиваются в один оператор INSERT, чтобы можно было использовать RETURNING.

Изменено в версии 2.0: ORM Session теперь интерпретирует условия RETURNING из конструкций Insert, Update и даже Delete в контексте ORM, то есть в метод Insert.returning() может быть передана смесь выражений столбцов и отображенных сущностей ORM, которые затем будут переданы так, как результаты ORM передаются из конструкций типа Select, включая то, что отображенные сущности будут переданы в результат как отображенные объекты ORM. Присутствует также ограниченная поддержка таких опций ORM-загрузчика, как load_only() и selectinload().

Соотнесение записей о возврате с порядком входных данных

При использовании массового INSERT с RETURNING важно учитывать, что большинство бэкендов баз данных не дают формальной гарантии порядка возврата записей из RETURNING, в том числе нет гарантии, что их порядок будет соответствовать порядку входных записей. Для приложений, которым необходимо обеспечить корреляцию записей RETURNING с входными данными, можно указать дополнительный параметр Insert.returning.sort_by_parameter_order, который в зависимости от бэкенда может использовать специальные формы INSERT, сохраняющие маркер, который используется для соответствующего упорядочивания возвращаемых строк, или в некоторых случаях, как в приведенном ниже примере с использованием бэкенда SQLite, операция будет выполнять INSERT по одной строке за раз:

>>> 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
... )
{execsql}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')
{stop}>>> 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}]

Добавлено в версии 2.0.10: Добавлена архитектура Insert.returning.sort_by_parameter_order, которая реализована в рамках архитектуры insertmanyvalues.

См.также

Соотнесение строк RETURNING с наборами параметров - описание подходов, позволяющих гарантировать соответствие между входными данными и строками результатов без существенной потери производительности

Использование гетерогенных словарей параметров

Функция массовой вставки ORM поддерживает списки словарей параметров, которые являются «гетерогенными», что в основном означает «отдельные словари могут иметь разные ключи». При обнаружении этого условия ORM разбивает словари параметров на группы, соответствующие каждому набору ключей, и объединяет их в отдельные операторы INSERT:

>>> 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"},
...     ],
... )
{execsql}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')

В приведенном примере пять словарей параметров были преобразованы в три оператора INSERT, сгруппированных по определенным наборам ключей в каждом словаре с сохранением порядка следования, т.е. ("name", "fullname", "species"), ("name", "species"), ("name","fullname", "species").

Bulk INSERT для наследования объединенных таблиц

Массовая вставка ORM опирается на внутреннюю систему, используемую традиционной системой unit of work для создания операторов INSERT. Это означает, что для сущности ORM, сопоставленной с несколькими таблицами, обычно сопоставленными с помощью joined table inheritance, операция bulk INSERT будет выдавать оператор INSERT для каждой таблицы, представленной сопоставлением, корректно передавая сгенерированные сервером значения первичных ключей в строки таблицы, которые от них зависят. Здесь также поддерживается функция RETURNING, при которой ORM будет получать объекты Result для каждого выполненного оператора INSERT, а затем «горизонтально сращивать» их вместе, чтобы возвращаемые строки включали значения для всех вставленных столбцов:

>>> managers = session.scalars(
...     insert(Manager).returning(Manager),
...     [
...         {"name": "sandy", "manager_name": "Sandy Cheeks"},
...         {"name": "ehkrabs", "manager_name": "Eugene H. Krabs"},
...     ],
... )
{execsql}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')

Совет

Массовая INSERT объединенных наследований требует, чтобы ORM использовал внутренний параметр Insert.returning.sort_by_parameter_order, чтобы он мог соотнести значения первичных ключей из строк RETURNING базовой таблицы с наборами параметров, используемых для INSERT в «подтаблицу», поэтому бэкенд SQLite, показанный выше, прозрачно переходит к использованию непакетных операторов. Подробнее об этой возможности рассказано в Соотнесение строк RETURNING с наборами параметров.

ORM Bulk Insert с использованием SQL-выражений

Функция массовой вставки ORM поддерживает добавление фиксированного набора параметров, которые могут включать SQL-выражения, применяемые к каждой целевой строке. Для этого необходимо сочетать использование метода Insert.values(), передающего словарь параметров, которые будут применены ко всем строкам, с обычной формой массового вызова, включающей при вызове Session.execute() список словарей параметров, содержащих значения отдельных строк.

В качестве примера приведем ORM-отображение, содержащее столбец «timestamp»:

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]

Если мы хотим выполнить INSERT серии элементов LogRecord, каждый из которых имеет уникальное поле message, но при этом хотим применить SQL-функцию now() ко всем строкам, мы можем передать timestamp внутри Insert.values(), а затем передать дополнительные записи, используя режим «bulk»:

>>> 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"},
...     ],
... )
{execsql}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')


{stop}>>> 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 Bulk Insert с использованием SQL-выражений для каждой строки

В самом методе Insert.values() непосредственно размещается список словарей параметров. При использовании конструкции Insert таким образом, без передачи какого-либо списка словарей параметров в параметр Session.execute.params, режим bulk ORM insert не используется, а вместо этого оператор INSERT отображается точно так, как задано, и вызывается ровно один раз. Такой режим работы может быть полезен как в случае передачи SQL-выражений по каждой строке, так и при использовании операторов «upsert» с ORM, о чем рассказано далее в этой главе в ORM «upsert» Statements.

Ниже приведен надуманный пример INSERT, в котором используются SQL-выражения для каждой строки, а также демонстрируется Insert.returning() в таком виде:

>>> 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),
... )
{execsql}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')
{stop}>>> print(address_result.all())
[Address(email_address='sandy@company.com'),
 Address(email_address='spongebob@company.com'),
 Address(email_address='patrick@company.com')]

Поскольку режим вставки bulk ORM выше не используется, следующие возможности отсутствуют:

  • Joined table inheritance или другие многотабличные отображения не поддерживаются, так как для этого потребуется несколько операторов INSERT.

  • Heterogenous parameter sets не поддерживаются - каждый элемент в наборе VALUES должен иметь одинаковые столбцы.

  • Оптимизация масштаба на уровне ядра, такая как пакетная обработка, обеспечиваемая insertmanyvalues, недоступна; операторы должны убедиться, что общее количество параметров не превышает ограничений, накладываемых базой данных.

По указанным выше причинам, как правило, не рекомендуется использовать несколько наборов параметров с Insert.values() в операторах ORM INSERT, если нет четкого обоснования, которое заключается либо в использовании «upsert», либо в необходимости встраивания SQL-выражений для каждой строки в каждый набор параметров.

См.также

ORM «upsert» Statements

Legacy Session Bulk INSERT Methods

В состав Session входят унаследованные методы для выполнения «массовых» операций INSERT и UPDATE. Эти методы имеют общую реализацию с версиями SQLAlchemy 2.0, описанными в ORM Bulk INSERT Statements и ORM Bulk UPDATE по первичному ключу, однако лишены многих возможностей, в частности, поддержки RETURNING, а также поддержки синхронизации сессий.

Код, использующий, например, Session.bulk_insert_mappings(), можно портировать следующим образом, начиная с этого примера отображения:

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

Вышеприведенное выражается с помощью нового API следующим образом:

from sqlalchemy import insert

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

ORM «upsert» Statements

Некоторые бэкенды с SQLAlchemy могут включать специфичные для диалекта конструкции Insert, которые дополнительно позволяют выполнять «upserts», или INSERT, при котором существующая строка в наборе параметров превращается в приближенное к UPDATE выражение. Под «существующей строкой» могут подразумеваться строки, имеющие одинаковое значение первичного ключа, или другие индексированные столбцы в строке, которые считаются уникальными; это зависит от возможностей используемого бэкенда.

Диалекты, входящие в состав SQLAlchemy и включающие специфические для диалекта возможности API «upsert», следующие:

В частности, метод «upsert» обычно требует обращения к исходному утверждению, поэтому утверждение обычно строится в два отдельных шага.

Подобные конструкции могут иметь и сторонние бэкенды, например, упомянутые в Внешние диалекты.

Хотя в SQLAlchemy пока нет конструкции upsert, не зависящей от бэкенда, приведенные выше варианты Insert тем не менее совместимы с ORM, поскольку могут использоваться так же, как и сама конструкция Insert, описанная в ORM Bulk Insert с использованием SQL-выражений для каждой строки, т.е. путем встраивания нужных строк для INSERT в метод Insert.values(). В приведенном ниже примере функция SQLite insert() используется для генерации конструкции Insert, включающей поддержку «ON CONFLICT DO UPDATE». Оператор передается в Session.execute(), где выполняется обычным образом, с той лишь особенностью, что словари параметров, передаваемые в Insert.values(), интерпретируются как ключи атрибутов, сопоставленных ORM, а не как имена столбцов:

>>> 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)
{execsql}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')
{stop}<...>

Использование RETURNING в операторах upsert

С точки зрения SQLAlchemy ORM операторы upsert выглядят как обычные конструкции Insert, то есть Insert.returning() работает с операторами upsert точно так же, как это было продемонстрировано на примере ORM Bulk Insert с использованием SQL-выражений для каждой строки, так что передавать можно любое выражение столбца или соответствующий класс сущности ORM. Продолжая пример из предыдущего раздела:

>>> result = session.scalars(
...     stmt.returning(User), execution_options={"populate_existing": True}
... )
{execsql}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')
{stop}>>> 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')]

В приведенном примере используется RETURNING для возврата ORM-объектов для каждой строки, вставленной или поднятой оператором. В примере также добавлено использование опции выполнения Заполнить существующие. Эта опция указывает, что объекты User, которые уже присутствуют в Session для уже существующих строк, должны быть обновлены данными из новой строки. Для чистого оператора Insert эта опция не имеет значения, поскольку каждая создаваемая строка представляет собой совершенно новый идентификатор первичного ключа. Однако когда оператор Insert включает в себя также опции «upsert», он может выдавать результаты из строк, которые уже существуют и, следовательно, уже могут иметь идентификатор первичного ключа, представленный в Session объекте identity map.

ORM Bulk UPDATE по первичному ключу

Конструкция Update может использоваться с оператором Session.execute() аналогично тому, как используется оператор Insert, описанный в ORM Bulk INSERT Statements, передавая список из множества словарей параметров, каждый из которых представляет собой отдельную строку, соответствующую одному значению первичного ключа. Это использование не следует путать с более распространенным способом использования операторов Update с ORM, использующим явное предложение WHERE, который документирован в ORM UPDATE и DELETE с пользовательскими критериями WHERE.

Для «массового» варианта UPDATE конструкция update() создается в терминах класса ORM и передается методу Session.execute(); результирующий объект Update должен содержать без значений и, как правило, без критериев WHERE, т.е. метод Update.values() не используется, а Update.where() обычно не используется, но может быть использован в необычном случае, когда необходимо добавить дополнительные критерии фильтрации.

Передача конструкции Update вместе со списком словарей параметров, каждый из которых включает полное значение первичного ключа, вызовет режим bulk UPDATE by primary key для оператора, генерируя соответствующие критерии WHERE для соответствия каждой строке по первичному ключу, и используя executemany для выполнения каждого набора параметров в операторе UPDATE:

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

Обратите внимание, что каждый словарь параметров должен содержать полный первичный ключ для каждой записи, иначе будет выдана ошибка.

Как и в случае с функцией массового INSERT, здесь также поддерживаются разнородные списки параметров, в которых параметры будут сгруппированы в подпакеты выполнения UPDATE.

Изменено в версии 2.0.11: Дополнительные критерии WHERE можно комбинировать с ORM Bulk UPDATE по первичному ключу, используя метод Update.where() для добавления дополнительных критериев. Однако эти критерии всегда дополняют уже имеющиеся критерии WHERE, включающие значения первичного ключа.

Функция RETURNING недоступна при использовании функции «bulk UPDATE by primary key»; список словарей с несколькими параметрами обязательно использует DBAPI executemany, который в своем обычном виде, как правило, не поддерживает строки результатов.

Изменено в версии 2.0: Передача конструкции Update в метод Session.execute() вместе со списком словарей параметров теперь вызывает «массовое обновление», которое использует ту же функциональность, что и устаревший метод Session.bulk_update_mappings(). Это изменение поведения по сравнению с серией 1.x, где Update поддерживался только при явных критериях WHERE и инлайн VALUES.

Отключение функции Bulk ORM Update by Primary Key для оператора UPDATE с несколькими наборами параметров

Функция ORM Bulk Update by Primary Key, которая запускает оператор UPDATE для каждой записи, включающий критерии WHERE для каждого значения первичного ключа, автоматически используется, когда:

  1. приведенный оператор UPDATE относится к сущности ORM

  2. для выполнения оператора используется Session, а не Core Connection

  3. Передаваемые параметры представляют собой список словарей.

Для того чтобы вызвать оператор UPDATE без использования «ORM Bulk Update by Primary Key», вызовите оператор по отношению к Connection напрямую, используя метод Session.connection() для получения текущего Connection для транзакции:

>>> 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"},
...     ],
... )
{execsql}UPDATE user_account SET fullname=? WHERE user_account.name = ?
[...] [('Spongebob Squarepants', 'spongebob'), ('Patrick Star', 'patrick')]
{stop}<...>

Bulk UPDATE по первичному ключу для наследования объединенных таблиц

ORM bulk update имеет поведение, аналогичное ORM bulk insert при использовании отображений с объединенным наследованием таблиц; как описано в Bulk INSERT для наследования объединенных таблиц, операция bulk UPDATE будет выдавать оператор UPDATE для каждой таблицы, представленной в отображении, для которой заданные параметры включают значения, подлежащие обновлению (незатронутые таблицы пропускаются).

Пример:

>>> session.execute(
...     update(Manager),
...     [
...         {
...             "id": 1,
...             "name": "scheeks",
...             "manager_name": "Sandy Cheeks, President",
...         },
...         {
...             "id": 2,
...             "name": "eugene",
...             "manager_name": "Eugene H. Krabs, VP Marketing",
...         },
...     ],
... )
{execsql}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)]
{stop}<...>

Legacy Session Bulk UPDATE Methods

Как уже говорилось в Legacy Session Bulk INSERT Methods, метод Session.bulk_update_mappings() в Session является унаследованной формой массового обновления, которую ORM использует внутренне при интерпретации оператора update() с заданными параметрами первичного ключа; однако при использовании унаследованной версии такие возможности, как поддержка сеансовой синхронизации, не включаются.

Пример ниже:

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"},
    ],
)

Выражается с помощью нового API следующим образом:

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 и DELETE с пользовательскими критериями WHERE

Конструкции Update и Delete, построенные с использованием пользовательских критериев WHERE (то есть с помощью методов Update.where() и Delete.where()), могут быть вызваны в контексте ORM путем передачи их в Session.execute(), без использования параметра Session.execute.params. Для Update обновляемые значения должны передаваться с помощью Update.values().

Этот способ использования отличается от функции, описанной ранее в ORM Bulk UPDATE по первичному ключу, тем, что ORM использует заданное предложение WHERE как есть, а не фиксирует предложение WHERE по первичному ключу. Это означает, что один оператор UPDATE или DELETE может воздействовать сразу на множество строк.

В качестве примера ниже приводится UPDATE, который затрагивает поле «fullname» в нескольких строках

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

Для DELETE пример удаления строк на основе критериев:

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

Выбор стратегии синхронизации

При использовании update() или delete() в сочетании с ORM-выполнением с помощью Session.execute() появляется дополнительная ORM-специфическая функциональность, которая синхронизирует состояние, изменяемое оператором, с состоянием объектов, присутствующих в данный момент в identity map в Session. Под «синхронизацией» мы подразумеваем, что атрибуты UPDATEd будут обновлены новым значением или, по крайней мере, expired так, что при следующем обращении они будут заново заполнены новым значением, а объекты DELETEd будут переведены в состояние deleted.

Эта синхронизация управляется как «стратегия синхронизации», которая передается как строковая опция выполнения ORM, обычно с помощью словаря 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})
{execsql}UPDATE user_account SET fullname=? WHERE user_account.name = ?
[...] ('Squidward Tentacles', 'squidward')
{stop}<...>

Опция выполнения также может быть включена в сам оператор с помощью метода Executable.execution_options():

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

Поддерживаются следующие значения для synchronize_session:

  • 'auto' - используется по умолчанию. Стратегия 'fetch' будет использоваться на бэкендах, поддерживающих RETURNING, к которым относятся все SQLAlchemy-родные драйверы, кроме MySQL. Если RETURNING не поддерживается, то вместо него будет использоваться стратегия 'evaluate'.

  • 'fetch' - Получает идентификатор первичного ключа затронутых строк, выполняя SELECT перед UPDATE или DELETE, или используя RETURNING, если база данных поддерживает это, так что объекты в памяти, затронутые операцией, могут быть обновлены новыми значениями (обновления) или исключены из Session (удаления). Эта стратегия синхронизации может быть использована даже в том случае, если в данной конструкции update() или delete() явно указаны сущности или столбцы с использованием UpdateBase.returning().

    Изменено в версии 2.0: Явное UpdateBase.returning() может сочетаться со стратегией синхронизации 'fetch' при использовании UPDATE и DELETE с критериями WHERE, поддерживаемыми ORM. Фактический оператор будет содержать объединение столбцов между теми, которые требует стратегия 'fetch', и теми, которые были запрошены.

  • 'evaluate' - указывает на оценку критериев WHERE, заданных в операторе UPDATE или DELETE на языке Python, для поиска совпадающих объектов внутри Session. Такой подход не добавляет в операцию никаких обходов SQL, и при отсутствии поддержки RETURNING может быть более эффективным. Для операторов UPDATE или DELETE со сложными критериями стратегия 'evaluate' может не справиться с оценкой выражения в Python и выдать ошибку. В этом случае вместо нее следует использовать стратегию 'fetch'.

    Совет

    Если в выражении SQL используются пользовательские операторы с помощью функции Operators.op() или custom_op, то параметр Operators.op.python_impl может быть использован для указания функции Python, которая будет использоваться стратегией синхронизации "evaluate".

    Добавлено в версии 2.0.

    Предупреждение

    Стратегию "evaluate" следует избегать, если операция UPDATE будет выполняться для Session, имеющего много объектов с истекшим сроком хранения, поскольку для проверки объектов на соответствие заданным критериям WHERE потребуется их обновить, что приведет к появлению SELECT для каждого из них. В этом случае, особенно если бэкенд поддерживает RETURNING, следует предпочесть стратегию "fetch".

  • False - не синхронизировать сессию. Эта опция может быть полезна для бэкендов, не поддерживающих RETURNING, где стратегия "evaluate" не может быть использована. В этом случае состояние объектов в Session остается неизменным и не будет автоматически соответствовать выданному оператору UPDATE или DELETE, если такие объекты, которые обычно соответствовали бы совпавшим строкам, присутствуют.

Использование RETURNING с UPDATE/DELETE и пользовательскими критериями WHERE

Метод UpdateBase.returning() полностью совместим с UPDATE и DELETE с критериями WHERE, поддерживаемыми ORM. Для RETURNING: могут быть указаны полные объекты и/или столбцы ORM:

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

Поддержка RETURNING также совместима со стратегией синхронизации fetch, которая также использует RETURNING. ORM организует столбцы в RETURNING соответствующим образом, чтобы синхронизация прошла успешно и чтобы возвращаемый Result содержал запрашиваемые сущности и столбцы SQL в требуемом порядке.

Добавлено в версии 2.0: UpdateBase.returning() может использоваться для UPDATE и DELETE с поддержкой ORM, сохраняя при этом полную совместимость со стратегией синхронизации fetch.

UPDATE/DELETE с пользовательскими критериями WHERE для наследования объединенных таблиц

Функция UPDATE/DELETE с критерием WHERE, в отличие от ORM Bulk UPDATE по первичному ключу, выдает только один оператор UPDATE или DELETE на один вызов Session.execute(). Это означает, что при выполнении оператора update() или delete() в отношении отображения с несколькими таблицами, например, подкласса в отображении наследования объединенных таблиц, этот оператор должен соответствовать текущим возможностям бэкенда, которые могут заключаться в том, что бэкенд не поддерживает оператор UPDATE или DELETE, ссылающийся на несколько таблиц, или имеет лишь ограниченную поддержку этого. Это означает, что для таких отображений, как подклассы объединенного наследования, ORM-версия функции UPDATE/DELETE с критерием WHERE может использоваться лишь в ограниченной степени или не использоваться вообще, в зависимости от специфики.

Наиболее простым способом создания многорядного оператора UPDATE для подкласса объединенной таблицы является обращение только к этой подтаблице. Это означает, что конструкция Update() должна ссылаться только на атрибуты, локальные для таблицы подкласса, как в приведенном ниже примере:

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

В приведенной выше форме элементарным способом обращения к базовой таблице для поиска строк, который будет работать на любом бэкенде SQL, является использование подзапроса:

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

Для бэкендов, поддерживающих UPDATE…FROM, подзапрос может быть задан в виде дополнительного простого критерия WHERE, однако критерии между двумя таблицами должны быть указаны явным образом:

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

Предполагается, что при DELETE одновременно будут удалены строки как в базовой таблице, так и в подтаблице. Чтобы удалить множество строк объединенных объектов наследования без использования каскадных внешних ключей, выполните DELETE для каждой таблицы отдельно:

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

В целом, обычные процессы unit of work должны быть предпочтительными для обновления и удаления строк при объединенном наследовании и других многотабличных связях, за исключением случаев, когда использование пользовательских критериев WHERE оправдано с точки зрения производительности.

Унаследованные методы запросов

Функция UPDATE/DELETE с WHERE, поддерживаемая ORM, первоначально была частью ныне устаревшего объекта Query, в методах Query.update() и Query.delete(). Эти методы остаются доступными и предоставляют подмножество тех же функций, которые описаны в ORM UPDATE и DELETE с пользовательскими критериями WHERE. Основное отличие заключается в том, что в унаследованных методах не предусмотрена явная поддержка RETURNING.

См.также

Query.update()

Query.delete()

Вернуться на верх