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 strategy の lazy="write_only"
をターゲットの relationship()
に割り当てます。 WriteOnlyMapped
アノテーションは Mapped
アノテーションの代替形式で、オブジェクトのインスタンスでの WriteOnlyCollection
コレクション型の使用を示します。
上記の relationship()
の設定には、 account
オブジェクトが削除されたとき、および account_transactions
コレクションから AccountTransaction
オブジェクトが削除されたときに実行するアクションに固有の要素もいくつか含まれています。これらの要素は次のとおりです。
passive_deletes=True
- unit of work `は、 ``Account` が削除されたときにコレクションをロードする必要がなくなります。 Using foreign key ON DELETE cascade with ORM relationships を参照してください。ondelete="cascade"
-ForeignKey
制約で設定されます。これについては Using foreign key ON DELETE cascade with ORM relationships でも詳しく説明されています。cascade="all, delete-orphan"
- unitofwork に対して、コレクションから削除されたAccountTransaction
オブジェクトを削除するよう指示します。 Cascades ドキュメントの delete-orphan を参照してください。
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¶
WriteOnlyCollection
は Insert
オブジェクトのような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
See also
Many to Many Collections¶
多対多のコレクション の場合、2つのクラス間の関係には、 relationship
の relationship.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)
See also
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 |
---|---|
Write-only collection which can synchronize changes into the attribute event system. |
|
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 withrelationship()
. For background on this configuration, see Write Only Relationships.New in version 2.0.
See also
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-localWriteOnlyCollection
.
-
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-localWriteOnlyCollection
.This construct is only supported for a
Relationship
that does not include therelationship.secondary
parameter. For relationships that refer to a many-to-many table, use ordinary bulk insert techniques to produce new objects, then useAbstractCollectionWriter.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-localWriteOnlyCollection
.
-
method
sqlalchemy.orm.WriteOnlyCollection.
update() Update ¶ Produce a
Update
which will refer to rows in terms of this instance-localWriteOnlyCollection
.
-
method
- 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 thelazy="write_only"
loader strategy should be used for a particularrelationship()
.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 legacyQuery
supportClass 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 |
---|---|
A dynamic query that supports basic collection storage operations. |
|
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 ofQuery
, 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 ofAppenderMixin
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 ofAppenderMixin
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 ofAppenderMixin
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 ofAppenderMixin
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 withSession.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)))
See also
-
method
sqlalchemy.orm.AppenderQuery.
extend(iterator: Iterable[_T]) None ¶ inherited from the
AppenderMixin.extend()
method ofAppenderMixin
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 ofAppenderMixin
Remove an item from this
AppenderQuery
.The given item will be removed from the parent instance’s collection on the next flush.
-
method
- 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 thelazy="dynamic"
loader strategy should be used for a particularrelationship()
.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 versionClass 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 を参照してください。