Working with Large Collections

relationship() のデフォルトの振る舞いは、コレクションの内容をいつ、どのようにデータベースからロードするかを制御する loader strategy の設定に基づいて、コレクションの内容を完全にメモリにロードすることです。関連するコレクションは、アクセスされたときや熱心にロードされたときだけでなく、ほとんどの場合、コレクション自体が変更されたときや、所有するオブジェクトが作業単位システムによって削除される場合にも、メモリにロードされます。

関連するコレクションが潜在的に非常に大きい場合、操作が時間、ネットワーク、およびメモリリソースを過度に消費する可能性があるため、いかなる状況においても、そのようなコレクションをメモリに格納することは実現不可能な場合がある。

この節には relationship() を適切な性能を保ちながら大きなコレクションで使えるようにするためのAPIの機能があります。

Write Only Relationships

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 strategylazy="write_only" をターゲットの relationship() に割り当てます。 WriteOnlyMapped アノテーションは Mapped アノテーションの代替形式で、オブジェクトのインスタンスでの WriteOnlyCollection コレクション型の使用を示します。

上記の relationship() の設定には、 account オブジェクトが削除されたとき、および account_transactions コレクションから AccountTransaction オブジェクトが削除されたときに実行するアクションに固有の要素もいくつか含まれています。これらの要素は次のとおりです。

New in version 2.0: “書き込み専用”の関係ローダーが追加されました。

Creating and Persisting New Write Only Collections

書き込み専用コレクションでは、 transient または pending オブジェクトに対して、コレクション全体を のみ 直接割り当てることができます。上記のマッピングでは、これは Session に追加される一連の AccountTransaction オブジェクトを持つ新しい Account オブジェクトを作成できることを示しています。開始するオブジェクトのソースとして任意のPython iterableを使用できますが、以下では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()
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

Adding New Items to an Existing Collection

永続オブジェクトの書き込み専用コレクションの場合、 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"))
BEGIN (implicit) SELECT account.id, account.identifier FROM account WHERE account.identifier = ? [...] ('account_01',)
>>> existing_account.account_transactions.add_all( ... [ ... AccountTransaction(description="paycheck", amount=Decimal("2000.00")), ... AccountTransaction(description="rent", amount=Decimal("-800.00")), ... ] ... ) >>> session.commit()
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 であったと仮定して、データベースに挿入されます。

Querying Items

WriteOnlyCollection は、コレクションの現在の内容への参照を格納しません。また、コレクションをロードするためにデータベースに対して直接SELECTを発行するような振る舞いもありません。最も重要な前提は、コレクションには何千、何百万もの行が含まれる可能性があり、他の操作の副作用として完全にメモリにロードされるべきではないということです。

代わりに、 WriteOnlyCollection には WriteOnlyCollection.select() のようなSQL生成ヘルパーが含まれています。これは現在の親行に対して正しいWHERE/FROM条件で事前に設定された Select 構文を生成します。この構文は、任意の範囲の行をSELECTするためにさらに変更することができます。また、 server side cursors のような機能を使って、メモリ効率の良い方法でコレクション全体を繰り返し処理したいプロセスに対して呼び出すこともできます。

生成される文を以下に示します。この文にはORDER BY条件も含まれることに注意してください。この条件は、マッピング例では relationship()relationship.order_by パラメータで指定されています。このパラメータが設定されていない場合、この条件は省略されます:

>>> print(existing_account.account_transactions.select())
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 オブジェクトを検索することができ、ORMオブジェクトを直接生成する Result を返す Session.scalars() メソッドを使うのが最も簡単です。必須ではありませんが、返されるレコードを制限するために Select をさらに修正するのが一般的です。下の例では、”debit” アカウントトランザクションだけをロードするための追加のWHERE条件が追加され、”LIMIT 10”が最初の10行だけを取得するために追加されています。:

>>> account_transactions = session.scalars(
...     existing_account.account_transactions.select()
...     .where(AccountTransaction.amount < 0)
...     .limit(10)
... ).all()
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)
>>> print(account_transactions) [AccountTransaction(amount=-29.50, timestamp='...'), AccountTransaction(amount=-800.00, timestamp='...')]

Removing Items

現在の Session に対して persistent 状態でロードされた個々の項目は、 WriteOnlyCollection.remove() メソッドを使用して、コレクションから削除するようにマークすることができます。フラッシュプロセスは、操作が進行するときに、暗黙的にオブジェクトがすでにコレクションの一部であると見なします。次の例は、個々の AccountTransaction 項目の削除を示しています。これは、 cascade 設定ごとに、その行のDELETEになります:

>>> existing_transaction = account_transactions[0]
>>> existing_account.account_transactions.remove(existing_transaction)
>>> session.commit()
DELETE FROM account_transaction WHERE account_transaction.id = ? [...] (3,) COMMIT

他のORMマップされたコレクションと同様に、オブジェクトの削除は、データベースに存在するオブジェクトを残したままコレクションからオブジェクトの関連付けを解除するか、 relationship()delete-orphan 設定に基づいて、その行に対してDELETEを発行するかのどちらかに進みます。

削除せずにコレクションを削除するには、 one-to-many 関係で外部キー列をNULLに設定するか、 many-to-many 関係で対応する関連付けの行を削除します。

Bulk INSERT of New Items

WriteOnlyCollectionInsert オブジェクトのようなDML構成体を生成することができます。これはORMコンテキストでバルクインサート動作を生成するために使用されます。ORM一括挿入の概要については ORM Bulk INSERT Statements を参照してください。

One to Many Collections

通常の1対多のコレクションのみ の場合、 WriteOnlyCollection.insert() メソッドは、親オブジェクトに対応するVALUES基準で事前に確立された Insert 構文を生成します。この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 Collections

多対多のコレクション の場合、2つのクラス間の関係には、 relationshiprelationship.secondary パラメータを使用して設定された3番目のテーブルが含まれます。 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
...     )

この2つの操作を説明するために、バルクインサートを使用してさらに AccountTransaction オブジェクトを追加し、バルクインサート文に returning(AccountTransaction) を追加してRETURNINGを使用して取得します(既存の 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()
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)

Bulk UPDATE and DELETE of Items

WriteOnlyCollection が事前に設定されたWHERE条件を持つ Select 構文を生成するのと同様に、同じWHERE条件を持つ Update および Delete 構文も生成できます。これにより、大きなコレクション内の要素に対して、条件指向のUPDATE文とDELETE文を使用できます。

One To Many Collections

INSERTの場合と同様に、この機能は 1つまたは複数のコレクション で最も簡単です。

以下の例では、 WriteOnlyCollection.update() メソッドを使ってコレクション内の要素に対してUPDATE文を生成し、”amount”が -800 に等しい行を見つけて、その行に 200 を加えます。:

>>> session.execute(
...     existing_account.account_transactions.update()
...     .values(amount=AccountTransaction.amount + 200)
...     .where(AccountTransaction.amount == -800),
... )
BEGIN (implicit) UPDATE account_transaction SET amount=(account_transaction.amount + ?) WHERE ? = account_transaction.account_id AND account_transaction.amount = ? [...] (200, 1, -800)
<...>

同様に、 WriteOnlyCollection.delete() は同じように呼び出されるDELETE文を生成します:

>>> session.execute(
...     existing_account.account_transactions.delete().where(
...         AccountTransaction.amount.between(0, 30)
...     ),
... )
DELETE FROM account_transaction WHERE ? = account_transaction.account_id AND account_transaction.amount BETWEEN ? AND ? RETURNING id [...] (1, 0, 30) <...>

Many to Many Collections

Tip

ここでのテクニックには、もう少し高度なマルチテーブルUPDATE式が含まれています。

多対多コレクション のバルクUPDATEおよびDELETEの場合、UPDATEまたはDELETE文が親オブジェクトのプライマリ・キーに関連するためには、関連付けテーブルがUPDATE/DELETE文の明示的な一部である必要があります。これには、バックエンドに非標準のSQL構文のサポートが含まれているか、UPDATEまたはDELETE文を構築するときに追加の明示的な手順が必要です。

複数テーブルバージョンのUPDATEをサポートするバックエンドでは、 WriteOnlyCollection.update() メソッドは、多対多のコレクションのための余分なステップなしで動作する必要があります。以下の例では、多対多の BankAudit.account_transactions コレクションの観点から、 AccountTransaction オブジェクトに対してUPDATEが発行されます。:

>>> session.execute(
...     bank_audit.account_transactions.update().values(
...         description=AccountTransaction.description + " (audited)"
...     )
... )
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)
<...>

上記の文は自動的にSQLiteなどでサポートされている”UPDATE.FROM”構文を使用して、WHERE句に追加の audit_transaction テーブルの名前を付けます。

複数テーブルの構文が使用できない場合に多対多のコレクションをUPDATEまたはDELETEするには、多対多の基準をSELECTに移動します。たとえば、行を一致させるためにINと組み合わせることができます。 WriteOnlyCollection は、 WriteOnlyCollection.select() メソッドを使用してこのSELECTを生成し、 Select.with_only_columns() メソッドを使用して:term:`スカラー副問い合わせ`を生成するので、ここでも役に立ちます:

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

Write Only Collections - API Documentation

Object Name Description

WriteOnlyCollection

Write-only collection which can synchronize changes into the attribute event system.

WriteOnlyMapped

Represent the ORM mapped attribute type for a “write only” relationship.

class sqlalchemy.orm.WriteOnlyCollection

Write-only collection which can synchronize changes into the attribute event system.

The WriteOnlyCollection is used in a mapping by using the "write_only" lazy loading strategy with relationship(). For background on this configuration, see Write Only Relationships.

New in version 2.0.

Class signature

class sqlalchemy.orm.WriteOnlyCollection (sqlalchemy.orm.writeonly.AbstractCollectionWriter)

method sqlalchemy.orm.WriteOnlyCollection.add(item: _T) None

Add an item to this WriteOnlyCollection.

The given item will be persisted to the database in terms of the parent instance’s collection on the next flush.

method sqlalchemy.orm.WriteOnlyCollection.add_all(iterator: Iterable[_T]) None

Add an iterable of items to this WriteOnlyCollection.

The given items will be persisted to the database in terms of the parent instance’s collection on the next flush.

method sqlalchemy.orm.WriteOnlyCollection.delete() Delete

Produce a Delete which will refer to rows in terms of this instance-local WriteOnlyCollection.

method sqlalchemy.orm.WriteOnlyCollection.insert() Insert

For one-to-many collections, produce a Insert which will insert new rows in terms of this this instance-local WriteOnlyCollection.

This construct is only supported for a Relationship that does not include the relationship.secondary parameter. For relationships that refer to a many-to-many table, use ordinary bulk insert techniques to produce new objects, then use AbstractCollectionWriter.add_all() to associate them with the collection.

method sqlalchemy.orm.WriteOnlyCollection.remove(item: _T) None

Remove an item from this WriteOnlyCollection.

The given item will be removed from the parent instance’s collection on the next flush.

method sqlalchemy.orm.WriteOnlyCollection.select() Select[Tuple[_T]]

Produce a Select construct that represents the rows within this instance-local WriteOnlyCollection.

method sqlalchemy.orm.WriteOnlyCollection.update() Update

Produce a Update which will refer to rows in terms of this instance-local WriteOnlyCollection.

class sqlalchemy.orm.WriteOnlyMapped

Represent the ORM mapped attribute type for a “write only” relationship.

The WriteOnlyMapped type annotation may be used in an Annotated Declarative Table mapping to indicate that the lazy="write_only" loader strategy should be used for a particular relationship().

E.g.:

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    addresses: WriteOnlyMapped[Address] = relationship(
        cascade="all,delete-orphan"
    )

See the section Write Only Relationships for background.

New in version 2.0.

See also

Write Only Relationships - complete background

DynamicMapped - includes legacy Query support

Class signature

class sqlalchemy.orm.WriteOnlyMapped (sqlalchemy.orm.base._MappedAnnotationBase)

Dynamic Relationship Loaders

Legacy Feature

“dynamic”lazy loader strategyは、:ref:`write_only_relationship`で説明されている現在の”write_only”strategyのレガシー形式です。

“動的”な方法では、関連するコレクションからレガシーな Query オブジェクトが生成されます。しかし、”動的”な関係の主な欠点は、コレクションが完全に反復されるケースがいくつかあり、その中には明白でないものもあり、それはケースバイケースで慎重なプログラミングとテストによってのみ防ぐことができることです。したがって、真に大規模なコレクション管理には、 WriteOnlyCollection が優先されるべきです。

ダイナミックローダーは Asynchronous I/O (asyncio) 拡張とも互換性がありません。 Asyncio dynamic guidelines に示されているように、いくつかの制限付きで使用できますが、asyncioと完全に互換性のある WriteOnlyCollection が優先されるべきです。

動的なリレーションシップ戦略では、 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 オブジェクトの User.posts コレクションは、 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"))

動的関係の読み込み側は常にデータベースに問い合わせを行うので、基礎となるコレクションへの変更はデータがフラッシュされるまで見えません。しかし、”autoflush”が使用中の Session で有効になっている限り、コレクションが問い合わせを発行しようとするたびに自動的に行われます。

Dynamic Relationship Loaders - API

Object Name Description

AppenderQuery

A dynamic query that supports basic collection storage operations.

DynamicMapped

Represent the ORM mapped attribute type for a “dynamic” relationship.

class sqlalchemy.orm.AppenderQuery

A dynamic query that supports basic collection storage operations.

Methods on AppenderQuery include all methods of Query, plus additional methods used for collection persistence.

Class signature

class sqlalchemy.orm.AppenderQuery (sqlalchemy.orm.dynamic.AppenderMixin, sqlalchemy.orm.Query)

method sqlalchemy.orm.AppenderQuery.add(item: _T) None

inherited from the AppenderMixin.add() method of AppenderMixin

Add an item to this AppenderQuery.

The given item will be persisted to the database in terms of the parent instance’s collection on the next flush.

This method is provided to assist in delivering forwards-compatibility with the WriteOnlyCollection collection class.

New in version 2.0.

method sqlalchemy.orm.AppenderQuery.add_all(iterator: Iterable[_T]) None

inherited from the AppenderMixin.add_all() method of AppenderMixin

Add an iterable of items to this AppenderQuery.

The given items will be persisted to the database in terms of the parent instance’s collection on the next flush.

This method is provided to assist in delivering forwards-compatibility with the WriteOnlyCollection collection class.

New in version 2.0.

method sqlalchemy.orm.AppenderQuery.append(item: _T) None

inherited from the AppenderMixin.append() method of AppenderMixin

Append an item to this AppenderQuery.

The given item will be persisted to the database in terms of the parent instance’s collection on the next flush.

method sqlalchemy.orm.AppenderQuery.count() int

inherited from the AppenderMixin.count() method of AppenderMixin

Return a count of rows this the SQL formed by this Query would return.

This generates the SQL for this Query as follows:

SELECT count(1) AS count_1 FROM (
    SELECT <rest of query follows...>
) AS anon_1

The above SQL returns a single row, which is the aggregate value of the count function; the Query.count() method then returns that single integer value.

Warning

It is important to note that the value returned by count() is not the same as the number of ORM objects that this Query would return from a method such as the .all() method. The Query object, when asked to return full entities, will deduplicate entries based on primary key, meaning if the same primary key value would appear in the results more than once, only one object of that primary key would be present. This does not apply to a query that is against individual columns.

For fine grained control over specific columns to count, to skip the usage of a subquery or otherwise control of the FROM clause, or to use other aggregate functions, use expression.func expressions in conjunction with Session.query(), i.e.:

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

inherited from the AppenderMixin.extend() method of AppenderMixin

Add an iterable of items to this AppenderQuery.

The given items will be persisted to the database in terms of the parent instance’s collection on the next flush.

method sqlalchemy.orm.AppenderQuery.remove(item: _T) None

inherited from the AppenderMixin.remove() method of AppenderMixin

Remove an item from this AppenderQuery.

The given item will be removed from the parent instance’s collection on the next flush.

class sqlalchemy.orm.DynamicMapped

Represent the ORM mapped attribute type for a “dynamic” relationship.

The DynamicMapped type annotation may be used in an Annotated Declarative Table mapping to indicate that the lazy="dynamic" loader strategy should be used for a particular relationship().

Legacy Feature

The “dynamic” lazy loader strategy is the legacy form of what is now the “write_only” strategy described in the section Write Only Relationships.

E.g.:

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    addresses: DynamicMapped[Address] = relationship(
        cascade="all,delete-orphan"
    )

See the section Dynamic Relationship Loaders for background.

New in version 2.0.

See also

Dynamic Relationship Loaders - complete background

WriteOnlyMapped - fully 2.0 style version

Class signature

class sqlalchemy.orm.DynamicMapped (sqlalchemy.orm.base._MappedAnnotationBase)

Setting RaiseLoad

RaiseLoadを設定する

“raise”でロードされた関係は InvalidRequestError を発生させます。この属性は通常遅延ロードを発生させます:

class MyClass(Base):
    __tablename__ = "some_table"

    # ...

    children: Mapped[List[MyRelatedClass]] = relationship(lazy="raise")

上記では、 children コレクションへの属性アクセスは、それが以前に設定されていない場合に例外を発生させます。これには読み取りアクセスも含まれますが、コレクションは最初にロードしないと変更できないので、書き込みアクセスにも影響します。これの理論的根拠は、アプリケーションが特定のコンテキスト内で予期しない遅延ロードを発生させないようにすることです。SQLログを読み取って、必要なすべての属性が積極的にロードされたことを判断するのではなく、「raise」戦略では、アンロードされた属性がアクセスされた場合にすぐに発生させます。raise戦略は、 raiseload() ローダオプションを使用して、クエリオプションベースでも利用できます。

Using Passive Deletes

SQLAlchemyにおけるコレクション管理の重要な側面は、コレクションを参照するオブジェクトが削除された場合、SQLAlchemyはこのコレクション内にあるオブジェクトを考慮する必要があるということです。これらのオブジェクトは親から関連付けを解除する必要があります。これは、1対多のコレクションの場合、外部キー列がNULLに設定されるか、または cascade 設定に基づいて、これらの行に対してDELETEを発行することを意味します。

unit of work プロセスは、オブジェクトを行単位でのみ考慮します。つまり、DELETE操作は、コレクション内のすべての行がフラッシュプロセス内のメモリに完全にロードされなければならないことを意味します。これは大きなコレクションでは実現できないので、代わりに、外部キーのON DELETEルールを使用して行を自動的に更新または削除するデータベース自身の機能に依存しようとします。これは、行を処理するために実際にこれらの行をロードする必要がないように作業単位に指示します。作業単位は、 relationship() 構文で relationship.passive_deletes を設定することによって、この方法で動作するように指示できます。使用中の外部キー制約も正しく設定されている必要があります。

完全な”passive delete”設定の詳細については、 Using foreign key ON DELETE cascade with ORM relationships を参照してください。