Работа с большими коллекциями¶
Поведение relationship()
по умолчанию заключается в полной загрузке содержимого коллекций в память на основе настроенного loader strategy, который управляет тем, когда и как это содержимое загружается из базы данных. Связанные коллекции могут загружаться в память не только при обращении к ним или при нетерпеливой загрузке, но в большинстве случаев потребуют заселения при мутации самой коллекции, а также в случаях, когда объект-владелец должен быть удален системой единиц работы.
Если связанная коллекция потенциально очень велика, то заполнение такой коллекции в память может оказаться нецелесообразным ни при каких обстоятельствах, поскольку эта операция может потребовать чрезмерных затрат времени, сетевых ресурсов и памяти.
Этот раздел включает функции API, предназначенные для того, чтобы позволить использовать relationship()
с большими коллекциями, сохраняя при этом достаточную производительность.
Писать только отношения¶
Стратегия загрузчика write only является основным средством конфигурирования relationship()
, который будет оставаться доступным для записи, но не будет загружать свое содержимое в память. Ниже показана конфигурация ORM только для записи в современной декларативной форме с указанием типов:
>>> from decimal import Decimal
>>> from datetime import datetime
>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy import func
>>> from sqlalchemy.orm import DeclarativeBase
>>> from sqlalchemy.orm import Mapped
>>> from sqlalchemy.orm import mapped_column
>>> from sqlalchemy.orm import relationship
>>> from sqlalchemy.orm import Session
>>> from sqlalchemy.orm import WriteOnlyMapped
>>> class Base(DeclarativeBase):
... pass
>>> class Account(Base):
... __tablename__ = "account"
... id: Mapped[int] = mapped_column(primary_key=True)
... identifier: Mapped[str]
...
... account_transactions: WriteOnlyMapped["AccountTransaction"] = relationship(
... cascade="all, delete-orphan",
... passive_deletes=True,
... order_by="AccountTransaction.timestamp",
... )
...
... def __repr__(self):
... return f"Account(identifier={self.identifier!r})"
>>> class AccountTransaction(Base):
... __tablename__ = "account_transaction"
... id: Mapped[int] = mapped_column(primary_key=True)
... account_id: Mapped[int] = mapped_column(
... ForeignKey("account.id", ondelete="cascade")
... )
... description: Mapped[str]
... amount: Mapped[Decimal]
... timestamp: Mapped[datetime] = mapped_column(default=func.now())
...
... def __repr__(self):
... return (
... f"AccountTransaction(amount={self.amount:.2f}, "
... f"timestamp={self.timestamp.isoformat()!r})"
... )
...
... __mapper_args__ = {"eager_defaults": True}
Выше отношение account_transactions
настроено не с помощью обычной аннотации Mapped
, а с помощью аннотации WriteOnlyMapped
типа, которая во время выполнения присвоит loader strategy из lazy="write_only"
целевому relationship()
. Аннотация WriteOnlyMapped
является альтернативной формой аннотации Mapped
, которая указывает на использование типа коллекции WriteOnlyCollection
на экземплярах объекта.
Приведенная выше конфигурация relationship()
также включает несколько элементов, которые определяют, какие действия предпринимать при удалении объектов Account
, а также при удалении объектов AccountTransaction
из коллекции account_transactions
. Этими элементами являются:
passive_deletes=True
- позволяет unit of work отказаться от загрузки коллекции, когдаAccount
удаляется; см. Использование каскада внешних ключей ON DELETE с отношениями ORM.ondelete="cascade"
настроен на ограничениеForeignKey
. Это также подробно описано в Использование каскада внешних ключей ON DELETE с отношениями ORM.cascade="all, delete-orphan"
- предписывает unit of work удалятьAccountTransaction
объекты, когда они удаляются из коллекции. См. delete-orphan в документе Каскады.
Добавлено в версии 2.0: Добавлены загрузчики отношений «Только запись».
Создание и сохранение новых коллекций, доступных только для записи¶
Коллекция только для записи позволяет прямое присвоение коллекции в целом только для объектов transient или pending. С нашим приведенным выше отображением это означает, что мы можем создать новый объект Account
с последовательностью объектов AccountTransaction
, которые будут добавлены в Session
. В качестве источника объектов для начала может быть использована любая итерабельность Python, где ниже мы используем Python list
:
>>> new_account = Account(
... identifier="account_01",
... account_transactions=[
... AccountTransaction(description="initial deposit", amount=Decimal("500.00")),
... AccountTransaction(description="transfer", amount=Decimal("1000.00")),
... AccountTransaction(description="withdrawal", amount=Decimal("-29.50")),
... ],
... )
>>> with Session(engine) as session:
... session.add(new_account)
... session.commit()
{execsql}BEGIN (implicit)
INSERT INTO account (identifier) VALUES (?)
[...] ('account_01',)
INSERT INTO account_transaction (account_id, description, amount, timestamp)
VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp
[... (insertmanyvalues) 1/3 (ordered; batch not supported)] (1, 'initial deposit', 500.0)
INSERT INTO account_transaction (account_id, description, amount, timestamp)
VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp
[insertmanyvalues 2/3 (ordered; batch not supported)] (1, 'transfer', 1000.0)
INSERT INTO account_transaction (account_id, description, amount, timestamp)
VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp
[insertmanyvalues 3/3 (ordered; batch not supported)] (1, 'withdrawal', -29.5)
COMMIT
Как только объект помещен в базу данных (т.е. находится в состоянии persistent или detached), коллекция может быть расширена новыми элементами, а также может быть удалена. Однако коллекция больше не может быть переназначена с полной заменой коллекции, поскольку такая операция требует полной загрузки предыдущей коллекции в память для согласования старых записей с новыми:
>>> new_account.account_transactions = [
... AccountTransaction(description="some transaction", amount=Decimal("10.00"))
... ]
Traceback (most recent call last):
...
sqlalchemy.exc.InvalidRequestError: Collection "Account.account_transactions" does not
support implicit iteration; collection replacement operations can't be used
Добавление новых предметов в существующую коллекцию¶
Для коллекций постоянных объектов, доступных только для записи, модификации коллекции с помощью процессов unit of work могут происходить только с использованием методов WriteOnlyCollection.add()
, WriteOnlyCollection.add_all()
и WriteOnlyCollection.remove()
:
>>> from sqlalchemy import select
>>> session = Session(engine, expire_on_commit=False)
>>> existing_account = session.scalar(select(Account).filter_by(identifier="account_01"))
{execsql}BEGIN (implicit)
SELECT account.id, account.identifier
FROM account
WHERE account.identifier = ?
[...] ('account_01',)
{stop}
>>> existing_account.account_transactions.add_all(
... [
... AccountTransaction(description="paycheck", amount=Decimal("2000.00")),
... AccountTransaction(description="rent", amount=Decimal("-800.00")),
... ]
... )
>>> session.commit()
{execsql}INSERT INTO account_transaction (account_id, description, amount, timestamp)
VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp
[... (insertmanyvalues) 1/2 (ordered; batch not supported)] (1, 'paycheck', 2000.0)
INSERT INTO account_transaction (account_id, description, amount, timestamp)
VALUES (?, ?, ?, CURRENT_TIMESTAMP) RETURNING id, timestamp
[insertmanyvalues 2/2 (ordered; batch not supported)] (1, 'rent', -800.0)
COMMIT
Добавленные выше объекты хранятся в очереди ожидания внутри Session
до следующей промывки, после чего они вставляются в базу данных, предполагая, что добавленные объекты были ранее transient.
Запрос элементов¶
WriteOnlyCollection
ни в коем случае не хранит ссылку на текущее содержимое коллекции, и не имеет никакого поведения, при котором он бы напрямую выдавал SELECT к базе данных, чтобы загрузить их; главное предположение заключается в том, что коллекция может содержать многие тысячи или миллионы строк, и никогда не должна полностью загружаться в память в качестве побочного эффекта любой другой операции.
Вместо этого, WriteOnlyCollection
включает SQL-генерирующие помощники, такие как WriteOnlyCollection.select()
, которые генерируют конструкцию Select
, предварительно настроенную с правильными критериями WHERE / FROM для текущей родительской строки, которая затем может быть модифицирована для ВЫБОРА любого диапазона строк, а также вызвана с помощью таких функций, как server side cursors для процессов, которые хотят итерационно просмотреть всю коллекцию в эффективном для памяти режиме.
Сгенерированный оператор показан ниже. Обратите внимание, что он также включает критерий ORDER BY, обозначенный в примере отображения параметром relationship.order_by
relationship()
; этот критерий был бы опущен, если бы параметр не был настроен:
>>> print(existing_account.account_transactions.select())
{printsql}SELECT account_transaction.id, account_transaction.account_id, account_transaction.description,
account_transaction.amount, account_transaction.timestamp
FROM account_transaction
WHERE :param_1 = account_transaction.account_id ORDER BY account_transaction.timestamp
Мы можем использовать эту конструкцию Select
вместе с Session
для запроса объектов AccountTransaction
, проще всего с помощью метода Session.scalars()
, который вернет Result
, непосредственно возвращающий объекты ORM. Типично, хотя и не обязательно, что Select
будет модифицирован для ограничения возвращаемых записей; в примере ниже добавлены дополнительные критерии WHERE для загрузки только «дебетовых» операций по счету, а также «LIMIT 10» для получения только первых десяти строк:
>>> account_transactions = session.scalars(
... existing_account.account_transactions.select()
... .where(AccountTransaction.amount < 0)
... .limit(10)
... ).all()
{execsql}BEGIN (implicit)
SELECT account_transaction.id, account_transaction.account_id, account_transaction.description,
account_transaction.amount, account_transaction.timestamp
FROM account_transaction
WHERE ? = account_transaction.account_id AND account_transaction.amount < ?
ORDER BY account_transaction.timestamp LIMIT ? OFFSET ?
[...] (1, 0, 10, 0)
{stop}>>> print(account_transactions)
[AccountTransaction(amount=-29.50, timestamp='...'), AccountTransaction(amount=-800.00, timestamp='...')]
Удаление предметов¶
Отдельные элементы, загруженные в состоянии persistent против текущего Session
, могут быть помечены для удаления из коллекции с помощью метода WriteOnlyCollection.remove()
. При выполнении этой операции процесс flush будет неявно считать объект уже частью коллекции. Приведенный ниже пример иллюстрирует удаление отдельного элемента AccountTransaction
, что в соответствии с настройками cascade приводит к DELETE этого ряда:
>>> existing_transaction = account_transactions[0]
>>> existing_account.account_transactions.remove(existing_transaction)
>>> session.commit()
{execsql}DELETE FROM account_transaction WHERE account_transaction.id = ?
[...] (3,)
COMMIT
Как и в случае с любой ORM-сопоставленной коллекцией, удаление объекта может привести либо к деассоциации объекта из коллекции, оставляя объект в базе данных, либо к выдаче DELETE для его строки, основываясь на конфигурации delete-orphan в relationship()
.
Удаление коллекции без удаления включает установку столбцов внешнего ключа в NULL для отношения one-to-many или удаление соответствующей строки ассоциации для отношения many-to-many.
Массовая вставка новых элементов¶
WriteOnlyCollection
может генерировать конструкции DML, такие как объекты Insert
, которые могут быть использованы в контексте ORM для создания поведения массовой вставки. См. раздел ORM Bulk INSERT Statements для обзора массовых вставок ORM.
Коллекции от одной до многих¶
Только для регулярной коллекции один ко многим метод WriteOnlyCollection.insert()
создаст конструкцию Insert
, в которой предварительно установлены критерии VALUES, соответствующие родительскому объекту. Поскольку этот критерий VALUES полностью относится к связанной таблице, оператор может быть использован для INSERT новых строк, которые в то же время станут новыми записями в связанной коллекции:
>>> session.execute(
... existing_account.account_transactions.insert(),
... [
... {"description": "transaction 1", "amount": Decimal("47.50")},
... {"description": "transaction 2", "amount": Decimal("-501.25")},
... {"description": "transaction 3", "amount": Decimal("1800.00")},
... {"description": "transaction 4", "amount": Decimal("-300.00")},
... ],
... )
{execsql}BEGIN (implicit)
INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES (?, ?, ?, CURRENT_TIMESTAMP)
[...] [(1, 'transaction 1', 47.5), (1, 'transaction 2', -501.25), (1, 'transaction 3', 1800.0), (1, 'transaction 4', -300.0)]
<...>
{stop}
>>> session.commit()
COMMIT
Коллекции «многие ко многим¶
Для коллекции many to many collection отношения между двумя классами включают третью таблицу, которая конфигурируется с помощью параметра relationship.secondary
в relationship
. Для массовой вставки строк в коллекцию такого типа с помощью WriteOnlyCollection
, новые записи могут быть сначала вставлены отдельно, получены с помощью RETURNING, а затем эти записи переданы в метод WriteOnlyCollection.add_all()
, где процесс единицы работы продолжит сохранять их как часть коллекции.
Предположим, что класс BankAudit
ссылается на множество AccountTransaction
записей с помощью таблицы «многие ко многим»:
>>> from sqlalchemy import Table, Column
>>> audit_to_transaction = Table(
... "audit_transaction",
... Base.metadata,
... Column("audit_id", ForeignKey("audit.id", ondelete="CASCADE"), primary_key=True),
... Column(
... "transaction_id",
... ForeignKey("account_transaction.id", ondelete="CASCADE"),
... primary_key=True,
... ),
... )
>>> class BankAudit(Base):
... __tablename__ = "audit"
... id: Mapped[int] = mapped_column(primary_key=True)
... account_transactions: WriteOnlyMapped["AccountTransaction"] = relationship(
... secondary=audit_to_transaction, passive_deletes=True
... )
Чтобы проиллюстрировать эти две операции, мы добавляем больше объектов AccountTransaction
с помощью bulk insert, которые мы извлекаем с помощью RETURNING, добавляя returning(AccountTransaction)
к оператору bulk INSERT (обратите внимание, что мы могли бы так же легко использовать существующие объекты AccountTransaction
):
>>> new_transactions = session.scalars(
... existing_account.account_transactions.insert().returning(AccountTransaction),
... [
... {"description": "odd trans 1", "amount": Decimal("50000.00")},
... {"description": "odd trans 2", "amount": Decimal("25000.00")},
... {"description": "odd trans 3", "amount": Decimal("45.00")},
... ],
... ).all()
{execsql}BEGIN (implicit)
INSERT INTO account_transaction (account_id, description, amount, timestamp) VALUES
(?, ?, ?, CURRENT_TIMESTAMP), (?, ?, ?, CURRENT_TIMESTAMP), (?, ?, ?, CURRENT_TIMESTAMP)
RETURNING id, account_id, description, amount, timestamp
[...] (1, 'odd trans 1', 50000.0, 1, 'odd trans 2', 25000.0, 1, 'odd trans 3', 45.0)
{stop}
Когда список объектов AccountTransaction
готов, метод WriteOnlyCollection.add_all()
используется для связывания сразу многих строк с новым объектом BankAudit
:
>>> bank_audit = BankAudit()
>>> session.add(bank_audit)
>>> bank_audit.account_transactions.add_all(new_transactions)
>>> session.commit()
{execsql}INSERT INTO audit DEFAULT VALUES
[...] ()
INSERT INTO audit_transaction (audit_id, transaction_id) VALUES (?, ?)
[...] [(1, 10), (1, 11), (1, 12)]
COMMIT
Массовое UPDATE и DELETE элементов¶
Подобно тому, как WriteOnlyCollection
может генерировать конструкции Select
с заранее установленными критериями WHERE, он также может генерировать конструкции Update
и Delete
с теми же критериями WHERE, чтобы позволить критериально-ориентированные операторы UPDATE и DELETE для элементов в большой коллекции.
Коллекции от одной до многих¶
Как и в случае с INSERT, эта функция наиболее проста при работе с коллекциями один ко многим.
В приведенном ниже примере метод WriteOnlyCollection.update()
используется для генерации оператора UPDATE, который генерируется над элементами коллекции, находя строки, в которых «сумма» равна -800
и добавляя к ним сумму 200
:
>>> session.execute(
... existing_account.account_transactions.update()
... .values(amount=AccountTransaction.amount + 200)
... .where(AccountTransaction.amount == -800),
... )
{execsql}BEGIN (implicit)
UPDATE account_transaction SET amount=(account_transaction.amount + ?)
WHERE ? = account_transaction.account_id AND account_transaction.amount = ?
[...] (200, 1, -800)
{stop}<...>
Аналогичным образом, WriteOnlyCollection.delete()
создаст оператор DELETE, который вызывается тем же способом:
>>> session.execute(
... existing_account.account_transactions.delete().where(
... AccountTransaction.amount.between(0, 30)
... ),
... )
{execsql}DELETE FROM account_transaction WHERE ? = account_transaction.account_id
AND account_transaction.amount BETWEEN ? AND ? RETURNING id
[...] (1, 0, 30)
<...>
{stop}
Коллекции «многие ко многим¶
Совет
Здесь используются многотабличные выражения UPDATE, которые являются немного более сложными.
Для массового UPDATE и DELETE коллекций many to many collections, для того чтобы оператор UPDATE или DELETE относился к первичному ключу родительского объекта, таблица ассоциации должна быть явной частью оператора UPDATE/DELETE, что требует либо поддержки бэкендом нестандартных синтаксисов SQL, либо дополнительных явных шагов при построении оператора UPDATE или DELETE.
Для бэкендов, поддерживающих многотабличные версии UPDATE, метод WriteOnlyCollection.update()
должен работать без дополнительных шагов для коллекции многие-ко-многим, как в примере ниже, где UPDATE выдается для объектов AccountTransaction
в терминах коллекции многие-ко-многим BankAudit.account_transactions
:
>>> session.execute(
... bank_audit.account_transactions.update().values(
... description=AccountTransaction.description + " (audited)"
... )
... )
{execsql}UPDATE account_transaction SET description=(account_transaction.description || ?)
FROM audit_transaction WHERE ? = audit_transaction.audit_id
AND account_transaction.id = audit_transaction.transaction_id RETURNING id
[...] (' (audited)', 1)
{stop}<...>
Приведенный выше оператор автоматически использует синтаксис «UPDATE..FROM», поддерживаемый SQLite и другими, чтобы назвать дополнительную таблицу audit_transaction
в предложении WHERE.
Для UPDATE или DELETE коллекции «многие-ко-многим», где синтаксис многотабличности недоступен, критерии «многие-ко-многим» могут быть перенесены в SELECT, который, например, может быть объединен с IN для сопоставления строк. Здесь нам по-прежнему помогает WriteOnlyCollection
, поскольку мы используем метод WriteOnlyCollection.select()
для создания этого SELECT, используя метод Select.with_only_columns()
для создания scalar subquery:
>>> from sqlalchemy import update
>>> subq = bank_audit.account_transactions.select().with_only_columns(AccountTransaction.id)
>>> session.execute(
... update(AccountTransaction)
... .values(description=AccountTransaction.description + " (audited)")
... .where(AccountTransaction.id.in_(subq))
... )
{execsql}UPDATE account_transaction SET description=(account_transaction.description || ?)
WHERE account_transaction.id IN (SELECT account_transaction.id
FROM audit_transaction
WHERE ? = audit_transaction.audit_id AND account_transaction.id = audit_transaction.transaction_id)
RETURNING id
[...] (' (audited)', 1)
<...>
Коллекции только для записи - Документация API¶
Object Name | Description |
---|---|
Коллекция, доступная только для записи, которая может синхронизировать изменения в системе событий атрибутов. |
|
Представляет сопоставленный ORM тип атрибута для отношения «только запись». |
- class sqlalchemy.orm.WriteOnlyCollection¶
Коллекция, доступная только для записи, которая может синхронизировать изменения в системе событий атрибутов.
WriteOnlyCollection
используется в отображении при использовании стратегии ленивой загрузки"write_only"
сrelationship()
. Подробнее об этой конфигурации см. в разделе Писать только отношения.Добавлено в версии 2.0.
См.также
Классная подпись
класс
sqlalchemy.orm.WriteOnlyCollection
(sqlalchemy.orm.writeonly.AbstractCollectionWriter
)-
method
sqlalchemy.orm.WriteOnlyCollection.
add(item: _T) None ¶ Добавьте элемент к этому
WriteOnlyCollection
.Данный элемент будет сохранен в базе данных в терминах коллекции родительского экземпляра при следующем flush.
-
method
sqlalchemy.orm.WriteOnlyCollection.
add_all(iterator: Iterable[_T]) None ¶ Добавляет итерабель элементов к данному
WriteOnlyCollection
.Данные элементы будут сохранены в базе данных в терминах коллекции родительского экземпляра при следующей вспышке.
-
method
sqlalchemy.orm.WriteOnlyCollection.
delete() Delete ¶ Произведите
Delete
, который будет ссылаться на строки в терминах данного экземпляра-локальногоWriteOnlyCollection
.
-
method
sqlalchemy.orm.WriteOnlyCollection.
insert() Insert ¶ Для коллекций «один-ко-многим» создайте
Insert
, который будет вставлять новые строки в терминах данного экземпляра-локальногоWriteOnlyCollection
.Эта конструкция поддерживается только для
Relationship
, который нет включает параметрrelationship.secondary
. Для отношений, которые ссылаются на таблицу «многие-ко-многим», используйте обычные методы массовой вставки для создания новых объектов, а затем используйтеAbstractCollectionWriter.add_all()
, чтобы связать их с коллекцией.
-
method
sqlalchemy.orm.WriteOnlyCollection.
remove(item: _T) None ¶ Удалить элемент из этого
WriteOnlyCollection
.Данный элемент будет удален из коллекции родительского экземпляра при следующей очистке.
-
method
sqlalchemy.orm.WriteOnlyCollection.
select() Select[Tuple[_T]] ¶ Произведите конструкцию
Select
, которая представляет строки в данном экземпляре-локальномWriteOnlyCollection
.
-
method
sqlalchemy.orm.WriteOnlyCollection.
update() Update ¶ Произведите
Update
, который будет ссылаться на строки в терминах данного экземпляра-локальногоWriteOnlyCollection
.
-
method
- class sqlalchemy.orm.WriteOnlyMapped¶
Представляет сопоставленный ORM тип атрибута для отношения «только запись».
Аннотация типа
WriteOnlyMapped
может быть использована в отображении Annotated Declarative Table, чтобы указать, что стратегия загрузчикаlazy="write_only"
должна быть использована для конкретногоrelationship()
.Например:
class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) addresses: WriteOnlyMapped[Address] = relationship( cascade="all,delete-orphan" )
Справочную информацию см. в разделе Писать только отношения.
Добавлено в версии 2.0.
См.также
Писать только отношения - полный фон
DynamicMapped
- включает унаследованную поддержкуQuery
Классная подпись
класс
sqlalchemy.orm.WriteOnlyMapped
(sqlalchemy.orm.base._MappedAnnotationBase
)
Динамические загрузчики отношений¶
Legacy Feature
Стратегия «динамического» ленивого загрузчика - это унаследованная форма того, что сейчас является стратегией «только запись», описанной в разделе Писать только отношения.
Стратегия «динамической» связи создает наследуемый объект Query
из связанной коллекции. Однако основным недостатком «динамических» отношений является то, что существует несколько случаев, когда коллекция будет полностью итерироваться, некоторые из них неочевидны, и их можно предотвратить только с помощью тщательного программирования и тестирования в каждом конкретном случае. Поэтому для управления действительно большими коллекциями следует предпочесть WriteOnlyCollection
.
Динамический загрузчик также не совместим с расширением Асинхронный ввод/вывод (asyncio). Его можно использовать с некоторыми ограничениями, как указано в Asyncio dynamic guidelines, но опять же следует предпочесть WriteOnlyCollection
, который полностью совместим с asyncio.
Стратегия динамических отношений позволяет конфигурировать relationship()
, который при обращении к экземпляру возвращает наследуемый объект Query
вместо коллекции. Затем Query
может быть модифицирован таким образом, что коллекция базы данных может быть итерирована на основе критериев фильтрации. Возвращаемый объект Query
является экземпляром AppenderQuery
, который сочетает в себе поведение загрузки и итерации Query
вместе с элементарными методами мутации коллекции, такими как AppenderQuery.append()
и AppenderQuery.remove()
.
Стратегия «динамического» загрузчика может быть настроена с помощью декларативной формы с аннотацией типов, используя класс аннотаций DynamicMapped
:
from sqlalchemy.orm import DynamicMapped
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
posts: DynamicMapped[Post] = relationship()
Выше, коллекция User.posts
на отдельном объекте User
вернет объект AppenderQuery
, который является подклассом Query
, который также поддерживает основные операции мутации коллекции:
jack = session.get(User, id)
# filter Jack's blog posts
posts = jack.posts.filter(Post.headline == "this is a post")
# apply array slices
posts = jack.posts[5:20]
Динамические отношения поддерживают ограниченные операции записи с помощью методов AppenderQuery.append()
и AppenderQuery.remove()
:
oldpost = jack.posts.filter(Post.headline == "old post").one()
jack.posts.remove(oldpost)
jack.posts.append(Post("new post"))
Поскольку сторона чтения динамического отношения всегда запрашивает базу данных, изменения в базовой коллекции не будут видны до тех пор, пока данные не будут удалены. Однако, если на используемом Session
включена функция «autoflush», это будет происходить автоматически каждый раз, когда коллекция собирается выполнить запрос.
Загрузчики динамических отношений - API¶
Object Name | Description |
---|---|
Динамический запрос, поддерживающий основные операции хранения коллекции. |
|
Представляет тип атрибута, сопоставленный ORM, для «динамического» отношения. |
- class sqlalchemy.orm.AppenderQuery¶
Динамический запрос, поддерживающий основные операции хранения коллекции.
Методы на
AppenderQuery
включают все методыQuery
, плюс дополнительные методы, используемые для сохранения коллекции.Классная подпись
класс
sqlalchemy.orm.AppenderQuery
(sqlalchemy.orm.dynamic.AppenderMixin
,sqlalchemy.orm.Query
)-
method
sqlalchemy.orm.AppenderQuery.
add(item: _T) None ¶ наследуется от
AppenderMixin.add()
методаAppenderMixin
Добавьте элемент к этому
AppenderQuery
.Данный элемент будет сохранен в базе данных в терминах коллекции родительского экземпляра при следующем flush.
Этот метод предоставляется для обеспечения совместимости с классом коллекции
WriteOnlyCollection
.Добавлено в версии 2.0.
-
method
sqlalchemy.orm.AppenderQuery.
add_all(iterator: Iterable[_T]) None ¶ наследуется от
AppenderMixin.add_all()
методаAppenderMixin
Добавляет итерабель элементов к данному
AppenderQuery
.Данные элементы будут сохранены в базе данных в терминах коллекции родительского экземпляра при следующей вспышке.
Этот метод предоставляется для обеспечения совместимости с классом коллекции
WriteOnlyCollection
.Добавлено в версии 2.0.
-
method
sqlalchemy.orm.AppenderQuery.
append(item: _T) None ¶ наследуется от
AppenderMixin.append()
методаAppenderMixin
Добавьте элемент к этому
AppenderQuery
.Данный элемент будет сохранен в базе данных в терминах коллекции родительского экземпляра при следующем flush.
-
method
sqlalchemy.orm.AppenderQuery.
count() int ¶ наследуется от
AppenderMixin.count()
методаAppenderMixin
Возвращает количество строк, которые вернул бы SQL, сформированный этим
Query
.В результате SQL для этого запроса будет выглядеть следующим образом:
SELECT count(1) AS count_1 FROM ( SELECT <rest of query follows...> ) AS anon_1
Приведенный выше SQL возвращает один ряд, который является суммарным значением функции count; метод
Query.count()
затем возвращает это единственное целочисленное значение.Предупреждение
Важно отметить, что значение, возвращаемое функцией count(), не то же самое, что количество объектов ORM, которые этот запрос вернул бы из такого метода, как метод .all(). Объект
Query
, когда его просят вернуть полные сущности, будет дублировать записи на основе первичного ключа, то есть если одно и то же значение первичного ключа появится в результатах более одного раза, будет присутствовать только один объект с таким первичным ключом. Это не относится к запросу, направленному на отдельные столбцы.Для более тонкого контроля над конкретными столбцами для подсчета, чтобы пропустить использование подзапроса или иного контроля пункта FROM, или для использования других агрегатных функций, используйте выражения
expression.func
в сочетании сSession.query()
, т.е.:from sqlalchemy import func # count User records, without # using a subquery. session.query(func.count(User.id)) # return count of user "id" grouped # by "name" session.query(func.count(User.id)).\ group_by(User.name) from sqlalchemy import distinct # count distinct "name" values session.query(func.count(distinct(User.name)))
См.также
-
method
sqlalchemy.orm.AppenderQuery.
extend(iterator: Iterable[_T]) None ¶ наследуется от
AppenderMixin.extend()
методаAppenderMixin
Добавляет итерабель элементов к данному
AppenderQuery
.Данные элементы будут сохранены в базе данных в терминах коллекции родительского экземпляра при следующей вспышке.
-
method
sqlalchemy.orm.AppenderQuery.
remove(item: _T) None ¶ наследуется от
AppenderMixin.remove()
методаAppenderMixin
Удалить элемент из этого
AppenderQuery
.Данный элемент будет удален из коллекции родительского экземпляра при следующей очистке.
-
method
- class sqlalchemy.orm.DynamicMapped¶
Представляет тип атрибута, сопоставленный ORM, для «динамического» отношения.
Аннотация типа
DynamicMapped
может быть использована в отображении Annotated Declarative Table, чтобы указать, что стратегия загрузчикаlazy="dynamic"
должна быть использована для конкретногоrelationship()
.Legacy Feature
Стратегия «динамического» ленивого загрузчика - это унаследованная форма того, что сейчас является стратегией «только запись», описанной в разделе Писать только отношения.
Например:
class User(Base): __tablename__ = "user" id: Mapped[int] = mapped_column(primary_key=True) addresses: DynamicMapped[Address] = relationship( cascade="all,delete-orphan" )
Справочную информацию см. в разделе Динамические загрузчики отношений.
Добавлено в версии 2.0.
См.также
Динамические загрузчики отношений - полный фон
WriteOnlyMapped
- полностью версия в стиле 2.0Классная подпись
класс
sqlalchemy.orm.DynamicMapped
(sqlalchemy.orm.base._MappedAnnotationBase
)
Установка RaiseLoad¶
Отношения с «raise»-загрузкой будут выдавать InvalidRequestError
там, где атрибут обычно выдавал бы ленивую загрузку:
class MyClass(Base):
__tablename__ = "some_table"
# ...
children: Mapped[List[MyRelatedClass]] = relationship(lazy="raise")
Выше, доступ к атрибутам коллекции children
вызовет исключение, если она не была предварительно заполнена. Это относится к доступу на чтение, но для коллекций также влияет на доступ на запись, поскольку коллекции нельзя изменять без предварительной загрузки. Это необходимо для того, чтобы убедиться, что приложение не создает неожиданных ленивых загрузок в определенном контексте. Вместо того, чтобы читать журналы SQL, чтобы определить, что все необходимые атрибуты были загружены с нетерпением, стратегия «raise» заставит незагруженные атрибуты немедленно подниматься при обращении к ним. Стратегия «raise» также доступна на основе опции запроса с помощью опции загрузчика raiseload()
.
Использование пассивных удалений¶
Важным аспектом управления коллекциями в SQLAlchemy является то, что при удалении объекта, ссылающегося на коллекцию, SQLAlchemy необходимо учитывать объекты, находящиеся внутри этой коллекции. Эти объекты должны быть деассоциированы от родителя, что для коллекции типа «один ко многим» означает, что столбцы внешнего ключа устанавливаются в NULL, или, основываясь на настройках cascade, может потребоваться выдача DELETE для этих строк.
Процесс unit of work рассматривает объекты только по строкам, что означает, что операция DELETE подразумевает, что все строки коллекции должны быть полностью загружены в память в процессе flush. Это невыполнимо для больших коллекций, поэтому вместо этого мы стремимся полагаться на возможности базы данных автоматически обновлять или удалять строки с помощью правил внешних ключей ON DELETE, инструктируя блок работы отказаться от фактической загрузки этих строк для их обработки. Рабочий модуль может быть настроен на работу таким образом путем конфигурирования relationship.passive_deletes
на конструкцию relationship()
; используемые ограничения внешнего ключа также должны быть правильно настроены.
Более подробную информацию о полной конфигурации «пассивного удаления» см. в разделе Использование каскада внешних ключей ON DELETE с отношениями ORM.