Spaltenladeoptionen

Über dieses Dokument

Dieser Abschnitt stellt zusätzliche Optionen bezüglich des Ladens von Spalten vor. Die verwendeten Mappings umfassen Spalten, die große Zeichenkettenwerte speichern würden, für die wir möglicherweise einschränken möchten, wann sie geladen werden.

ORM-Setup für diese Seite anzeigen. Einige der unten stehenden Beispiele werden den Book-Mapper neu definieren, um einige der Spaltendefinitionen zu ändern.

Begrenzung der zu ladenden Spalten mit Spaltengruppierung

Spaltengruppierung bezieht sich auf ORM-zugeordnete Spalten, die von einer SELECT-Anweisung weggelassen werden, wenn Objekte dieses Typs abgefragt werden. Die allgemeine Begründung hierfür ist die Leistung, in Fällen, in denen Tabellen selten verwendete Spalten mit potenziell großen Datenwerten haben, da das vollständige Laden dieser Spalten bei jeder Abfrage zeit- und/oder speicherintensiv sein kann. SQLAlchemy ORM bietet eine Vielzahl von Möglichkeiten, das Laden von Spalten zu steuern, wenn Entitäten geladen werden.

Die meisten Beispiele in diesem Abschnitt veranschaulichen ORM-Ladeoptionen. Dies sind kleine Konstrukte, die an die Methode Select.options() des Objekts Select übergeben werden, welche dann vom ORM verbraucht werden, wenn das Objekt in eine SQL-Zeichenkette kompiliert wird.

Verwendung von load_only() zur Reduzierung geladener Spalten

Die Ladeoption load_only() ist die zweckmäßigste Option, wenn Objekte geladen werden, bei denen nur eine kleine Handvoll Spalten zugänglich sein wird. Diese Option akzeptiert eine variable Anzahl von klassenbasierten Attributobjekten, die die spaltengemanschten Attribute angeben, die geladen werden sollen, wobei alle anderen spaltengemanschten Attribute außerhalb des Primärschlüssels nicht Teil der abgerufenen Spalten sind. Im folgenden Beispiel enthält die Klasse Book die Spalten .title, .summary und .cover_photo. Mit load_only() können wir das ORM anweisen, nur die Spalten .title und .summary vorab zu laden.

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import load_only
>>> stmt = select(Book).options(load_only(Book.title, Book.summary))
>>> books = session.scalars(stmt).all()
SELECT book.id, book.title, book.summary FROM book [...] ()
>>> for book in books: ... print(f"{book.title} {book.summary}") 100 Years of Krabby Patties some long summary Sea Catch 22 another long summary The Sea Grapes of Wrath yet another summary A Nut Like No Other some long summary Geodesic Domes: A Retrospective another long summary Rocketry for Squirrels yet another summary

Oben hat die SELECT-Anweisung die Spalte .cover_photo weggelassen und nur .title und .summary sowie die Primärschlüsselspalte .id eingeschlossen; das ORM ruft typischerweise immer die Primärschlüsselspalten ab, da diese erforderlich sind, um die Identität für die Zeile festzustellen.

Nach dem Laden weist das Objekt normalerweise ein Lazy Loading-Verhalten für die verbleibenden ungeladenen Attribute auf, was bedeutet, dass bei jedem ersten Zugriff eine SQL-Anweisung innerhalb der aktuellen Transaktion gesendet wird, um den Wert zu laden. Unten emittiert der Zugriff auf .cover_photo eine SELECT-Anweisung, um seinen Wert zu laden.

>>> img_data = books[0].cover_photo
SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (1,)

Lazy Loads werden immer über die Session emittiert, der das Objekt im persistenten Zustand ist. Wenn das Objekt von einer Session detached ist, schlägt die Operation fehl und löst eine Ausnahme aus.

Als Alternative zum Lazy Loading beim Zugriff können deferrte Spalten auch so konfiguriert werden, dass beim Zugriff eine informative Ausnahme ausgelöst wird, unabhängig von ihrem Anbindungsstatus. Bei Verwendung des Konstrukts load_only() kann dies über den Parameter load_only.raiseload angezeigt werden. Hintergrundinformationen und Beispiele finden Sie im Abschnitt Verwendung von raiseload zur Verhinderung von deferrten Spaltenladungen.

Tipp

Wie anderswo angemerkt, ist Lazy Loading bei der Verwendung von Asynchronem I/O (asyncio) nicht verfügbar.

Verwendung von load_only() mit mehreren Entitäten

load_only() beschränkt sich auf die einzelne Entität, auf die in ihrer Attributliste verwiesen wird (das Übergeben einer Liste von Attributen, die mehr als eine einzelne Entität umfassen, ist derzeit nicht zulässig). Im folgenden Beispiel gilt die angegebene load_only()-Option nur für die Entität Book. Die ebenfalls ausgewählte Entität User ist nicht betroffen; in der resultierenden SELECT-Anweisung sind alle Spalten für user_account vorhanden, während für die Tabelle book nur book.id und book.title vorhanden sind.

>>> stmt = select(User, Book).join_from(User, Book).options(load_only(Book.title))
>>> print(stmt)
SELECT user_account.id, user_account.name, user_account.fullname, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id

Wenn wir load_only()-Optionen sowohl für User als auch für Book anwenden möchten, würden wir zwei separate Optionen verwenden.

>>> stmt = (
...     select(User, Book)
...     .join_from(User, Book)
...     .options(load_only(User.name), load_only(Book.title))
... )
>>> print(stmt)
SELECT user_account.id, user_account.name, book.id AS id_1, book.title FROM user_account JOIN book ON user_account.id = book.owner_id

Verwendung von defer() zum Auslassen spezifischer Spalten

Die Ladeoption defer() ist eine feinere Alternative zu load_only(), die es ermöglicht, eine einzelne spezifische Spalte als "nicht laden" zu markieren. Im folgenden Beispiel wird defer() direkt auf die Spalte .cover_photo angewendet, während das Verhalten aller anderen Spalten unverändert bleibt.

>>> from sqlalchemy.orm import defer
>>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo))
>>> books = session.scalars(stmt).all()
SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.owner_id = ? [...] (2,)
>>> for book in books: ... print(f"{book.title}: {book.summary}") A Nut Like No Other: some long summary Geodesic Domes: A Retrospective: another long summary Rocketry for Squirrels: yet another summary

Wie bei load_only() werden ungeladene Spalten standardmäßig geladen, wenn sie über Lazy Loading abgerufen werden.

>>> img_data = books[0].cover_photo
SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (4,)

Mehrere defer()-Optionen können in einer Anweisung verwendet werden, um mehrere Spalten als deferrt zu markieren.

Wie bei load_only() enthält die Option defer() ebenfalls die Möglichkeit, dass ein deferrtes Attribut beim Zugriff eine Ausnahme auslöst, anstatt Lazy Loading. Dies wird im Abschnitt Verwendung von raiseload zur Verhinderung von deferrten Spaltenladungen veranschaulicht.

Verwendung von raiseload zur Verhinderung von deferrten Spaltenladungen

Bei Verwendung der Ladeoptionen load_only() oder defer() haben Attribute, die als deferrt auf einem Objekt markiert sind, das Standardverhalten, dass beim ersten Zugriff eine SELECT-Anweisung innerhalb der aktuellen Transaktion gesendet wird, um ihren Wert zu laden. Es ist oft notwendig, diese Ladung zu verhindern und stattdessen eine Ausnahme auszulösen, wenn auf das Attribut zugegriffen wird, was darauf hinweist, dass die Notwendigkeit, die Datenbank nach dieser Spalte abzufragen, nicht erwartet wurde. Ein typisches Szenario ist eine Operation, bei der Objekte mit allen Spalten geladen werden, die für den ordnungsgemäßen Ablauf der Operation als erforderlich bekannt sind und die dann an eine View-Schicht weitergegeben werden. Alle weiteren SQL-Operationen, die in der View-Schicht ausgelöst werden, sollten abgefangen werden, damit die vorab erfolgte Ladeoperation angepasst werden kann, um diese zusätzlichen Daten vorab zu berücksichtigen, anstatt zusätzliche Lazy Loads zu verursachen.

Für diesen Anwendungsfall enthalten die Optionen defer() und load_only() einen booleschen Parameter defer.raiseload, der, wenn er auf True gesetzt ist, dazu führt, dass die betroffenen Attribute beim Zugriff fehlschlagen. Im folgenden Beispiel erlaubt die deferrte Spalte .cover_photo keinen Attributzugriff.

>>> book = session.scalar(
...     select(Book).options(defer(Book.cover_photo, raiseload=True)).where(Book.id == 4)
... )
SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.id = ? [...] (4,)
>>> book.cover_photo Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.cover_photo' is not available due to raiseload=True

Bei Verwendung von load_only() zur Benennung einer bestimmten Menge von nicht-deferrten Spalten kann das raiseload-Verhalten für die übrigen Spalten über den Parameter load_only.raiseload angewendet werden, der auf alle deferrten Attribute angewendet wird.

>>> session.expunge_all()
>>> book = session.scalar(
...     select(Book).options(load_only(Book.title, raiseload=True)).where(Book.id == 5)
... )
SELECT book.id, book.title FROM book WHERE book.id = ? [...] (5,)
>>> book.summary Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

Hinweis

Es ist noch nicht möglich, load_only() und defer()-Optionen, die sich auf dieselbe Entität beziehen, gemeinsam in einer Anweisung zu mischen, um das raiseload-Verhalten bestimmter Attribute zu ändern; derzeit führt dies zu einem undefinierten Ladeverhalten von Attributen.

Siehe auch

Das Feature defer.raiseload ist die Spalten-Level-Version desselben "raiseload"-Features, das für Beziehungen verfügbar ist. Für "raiseload" mit Beziehungen siehe Verhinderung unerwünschter Lazy Loads mit raiseload im Abschnitt Techniken zum Laden von Beziehungen dieses Leitfadens.

Konfiguration der Spaltengruppierung auf Mapping-Ebene

Die Funktionalität von defer() ist als Standardverhalten für zugeordnete Spalten verfügbar, da dies für Spalten angemessen sein kann, die nicht bedingungslos bei jeder Abfrage geladen werden sollten. Zur Konfiguration verwenden Sie den Parameter mapped_column.deferred von mapped_column(). Das folgende Beispiel veranschaulicht ein Mapping für Book, das die standardmäßige Spaltengruppierung auf die Spalten summary und cover_photo anwendet.

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True)
...     cover_photo: Mapped[bytes] = mapped_column(LargeBinary, deferred=True)
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

Mit dem obigen Mapping schließen Abfragen gegen Book automatisch die Spalten summary und cover_photo aus.

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT book.id, book.owner_id, book.title FROM book WHERE book.id = ? [...] (2,)

Wie bei jeder Gruppierung ist das Standardverhalten, wenn die deferrten Attribute im geladenen Objekt zum ersten Mal aufgerufen werden, dass sie ihren Wert per Lazy Load laden.

>>> img_data = book.cover_photo
SELECT book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (2,)

Wie bei den Ladeoptionen defer() und load_only() enthält die Mapper-Level-Gruppierung auch eine Option für das raiseload-Verhalten, anstatt Lazy Loading, wenn keine anderen Optionen in einer Anweisung vorhanden sind. Dies ermöglicht ein Mapping, bei dem bestimmte Spalten standardmäßig nicht geladen werden und auch nie lazy geladen werden, ohne explizite Anweisungen in einer Anweisung. Weitere Informationen zur Konfiguration und Verwendung dieses Verhaltens finden Sie im Abschnitt Konfiguration des Mapper-Level-Verhaltens "raiseload".

Verwendung von deferred() für imperative Mapper, zugeordnete SQL-Ausdrücke

Die Funktion deferred() ist die frühere, allgemeinere Direktive für "deferrte Spaltenzuordnung", die der Einführung des Konstrukts mapped_column() in SQLAlchemy vorausgeht.

deferred() wird bei der Konfiguration von ORM-Mappern verwendet und akzeptiert beliebige SQL-Ausdrücke oder Column-Objekte. Daher eignet es sich für die Verwendung mit nicht-deklarativen imperativen Mappings und kann dem Wörterbuch map_imperatively.properties übergeben werden.

from sqlalchemy import Blob
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Text
from sqlalchemy.orm import registry

mapper_registry = registry()

book_table = Table(
    "book",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("title", String(50)),
    Column("summary", Text),
    Column("cover_image", Blob),
)


class Book:
    pass


mapper_registry.map_imperatively(
    Book,
    book_table,
    properties={
        "summary": deferred(book_table.c.summary),
        "cover_image": deferred(book_table.c.cover_image),
    },
)

deferred() kann auch anstelle von column_property() verwendet werden, wenn zugeordnete SQL-Ausdrücke verzögert geladen werden sollen.

from sqlalchemy.orm import deferred


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname + " " + lastname)

Verwendung von undefer() zum "Eager" Laden von deferrten Spalten

Wenn Spalten auf Mappings so konfiguriert sind, dass sie standardmäßig deferrt werden, bewirkt die Option undefer(), dass jede Spalte, die normalerweise deferrt ist, entgruppiert wird, d.h. sie wird zusammen mit allen anderen Spalten des Mappings vorab geladen. Wir können zum Beispiel undefer() auf die Spalte Book.summary anwenden, die im vorherigen Mapping als deferrt angegeben ist.

>>> from sqlalchemy.orm import undefer
>>> book = session.scalar(select(Book).where(Book.id == 2).options(undefer(Book.summary)))
SELECT book.id, book.owner_id, book.title, book.summary FROM book WHERE book.id = ? [...] (2,)

Die Spalte Book.summary wurde nun eager geladen und kann ohne zusätzliche SQL-Emissionen abgerufen werden.

>>> print(book.summary)
another long summary

Laden von deferrten Spalten in Gruppen

Normalerweise, wenn eine Spalte mit mapped_column(deferred=True) gemappt wird, wird beim Zugriff auf das deferrte Attribut auf einem Objekt SQL gesendet, um nur diese spezifische Spalte und keine anderen zu laden, selbst wenn das Mapping andere deferrte Spalten hat. In dem häufigen Fall, dass das deferrte Attribut Teil einer Gruppe von Attributen ist, die alle gleichzeitig geladen werden sollen, kann anstatt für jedes Attribut einzeln SQL zu senden, der Parameter mapped_column.deferred_group verwendet werden, der eine beliebige Zeichenkette akzeptiert, die eine gemeinsame Gruppe von Spalten definiert, die entgruppiert werden sollen.

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(
...         Text, deferred=True, deferred_group="book_attrs"
...     )
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_group="book_attrs"
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

Mit dem obigen Mapping werden beim Zugriff auf summary oder cover_photo beide Spalten auf einmal mit nur einer SELECT-Anweisung geladen.

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT book.id, book.owner_id, book.title FROM book WHERE book.id = ? [...] (2,)
>>> img_data, summary = book.cover_photo, book.summary
SELECT book.summary AS book_summary, book.cover_photo AS book_cover_photo FROM book WHERE book.id = ? [...] (2,)

Aufheben der Gruppierung mit undefer_group()

Wenn deferrte Spalten mit mapped_column.deferred_group wie im vorherigen Abschnitt eingeführt konfiguriert sind, kann die gesamte Gruppe über die Option undefer_group() mit dem Zeichenkettennamen der eager zu ladenden Gruppe vorab geladen werden.

>>> from sqlalchemy.orm import undefer_group
>>> book = session.scalar(
...     select(Book).where(Book.id == 2).options(undefer_group("book_attrs"))
... )
SELECT book.id, book.owner_id, book.title, book.summary, book.cover_photo FROM book WHERE book.id = ? [...] (2,)

Sowohl summary als auch cover_photo sind ohne zusätzliche Ladevorgänge verfügbar.

>>> img_data, summary = book.cover_photo, book.summary

Aufheben der Gruppierung mit Platzhaltern

Die meisten ORM-Ladeoptionen akzeptieren einen Platzhaltersatz, der durch "*" gekennzeichnet ist und angibt, dass die Option auf alle relevanten Attribute angewendet werden soll. Wenn ein Mapping eine Reihe von deferrten Spalten aufweist, können alle diese Spalten auf einmal und ohne Gruppennamen entgruppiert werden, indem ein Platzhalter angegeben wird.

>>> book = session.scalar(select(Book).where(Book.id == 3).options(undefer("*")))
SELECT book.id, book.owner_id, book.title, book.summary, book.cover_photo FROM book WHERE book.id = ? [...] (3,)

Konfiguration des Mapper-Level-Verhaltens "raiseload"

Das Verhalten "raiseload", das zuerst in Verwendung von raiseload zur Verhinderung von deferrten Spaltenladungen eingeführt wurde, kann auch als standardmäßiges Mapper-Level-Verhalten angewendet werden, mit dem Parameter mapped_column.deferred_raiseload von mapped_column(). Bei Verwendung dieses Parameters werden die betroffenen Spalten standardmäßig beim Zugriff fehlschlagen, es sei denn, sie werden explizit mit undefer() oder load_only() zur Abfragezeit "entgruppiert".

>>> class Book(Base):
...     __tablename__ = "book"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     owner_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
...     title: Mapped[str]
...     summary: Mapped[str] = mapped_column(Text, deferred=True, deferred_raiseload=True)
...     cover_photo: Mapped[bytes] = mapped_column(
...         LargeBinary, deferred=True, deferred_raiseload=True
...     )
...
...     def __repr__(self) -> str:
...         return f"Book(id={self.id!r}, title={self.title!r})"

Mit dem obigen Mapping sind die Spalten .summary und .cover_photo standardmäßig nicht ladbar.

>>> book = session.scalar(select(Book).where(Book.id == 2))
SELECT book.id, book.owner_id, book.title FROM book WHERE book.id = ? [...] (2,)
>>> book.summary Traceback (most recent call last): ... sqlalchemy.exc.InvalidRequestError: 'Book.summary' is not available due to raiseload=True

Nur durch Überschreiben ihres Verhaltens zur Abfragezeit, typischerweise mit undefer() oder undefer_group(), oder seltener mit defer(), können die Attribute geladen werden. Das folgende Beispiel wendet undefer('*') an, um alle Attribute zu entgruppieren, und verwendet außerdem Populate Existing, um die bereits geladenen Loader-Optionen des Objekts zu aktualisieren.

>>> book = session.scalar(
...     select(Book)
...     .where(Book.id == 2)
...     .options(undefer("*"))
...     .execution_options(populate_existing=True)
... )
SELECT book.id, book.owner_id, book.title, book.summary, book.cover_photo FROM book WHERE book.id = ? [...] (2,)
>>> book.summary 'another long summary'

Laden beliebiger SQL-Ausdrücke in Objekte

Wie in Auswahl von ORM-Entitäten und Attributen und anderswo diskutiert, kann die Konstruktion select() verwendet werden, um beliebige SQL-Ausdrücke in einem Ergebnissatz zu laden. So könnten wir beispielsweise eine Abfrage ausgeben, die User-Objekte lädt, aber auch eine Anzahl, wie viele Bücher jeder User besaß, beinhaltet. Wir könnten func.count(Book.id) verwenden, um eine "count"-Spalte zu einer Abfrage hinzuzufügen, die einen JOIN zu Book sowie eine GROUP BY owner id enthält. Dies liefert Row-Objekte, die jeweils zwei Einträge enthalten, einen für User und einen für func.count(Book.id).

>>> from sqlalchemy import func
>>> stmt = select(User, func.count(Book.id)).join_from(User, Book).group_by(Book.owner_id)
>>> for user, book_count in session.execute(stmt):
...     print(f"Username: {user.name}  Number of books: {book_count}")
SELECT user_account.id, user_account.name, user_account.fullname, count(book.id) AS count_1 FROM user_account JOIN book ON user_account.id = book.owner_id GROUP BY book.owner_id [...] ()
Username: spongebob Number of books: 3 Username: sandy Number of books: 3

Im obigen Beispiel werden die Entität User und der SQL-Ausdruck "book count" getrennt zurückgegeben. Ein beliebter Anwendungsfall ist jedoch die Erzeugung einer Abfrage, die nur User-Objekte liefert, die beispielsweise mit Session.scalars() durchlaufen werden können, wobei das Ergebnis des SQL-Ausdrucks func.count(Book.id) *dynamisch* auf jede User-Entität angewendet wird. Das Endergebnis wäre ähnlich wie in dem Fall, dass ein beliebiger SQL-Ausdruck über column_property() der Klasse zugeordnet wurde, mit dem Unterschied, dass der SQL-Ausdruck zur Abfragezeit geändert werden kann. Für diesen Anwendungsfall bietet SQLAlchemy die Ladeoption with_expression(), die in Kombination mit der Mapper-Level-Direktive query_expression() dieses Ergebnis erzielen kann.

Um with_expression() auf eine Abfrage anzuwenden, muss die zugeordnete Klasse ein ORM-zugeordnetes Attribut über die query_expression()-Direktive vorkonfiguriert haben; diese Direktive erzeugt ein Attribut auf der zugeordneten Klasse, das für den Empfang von Abfragezeit-SQL-Ausdrücken geeignet ist. Unten fügen wir der Klasse User ein neues Attribut User.book_count hinzu. Dieses ORM-zugeordnete Attribut ist schreibgeschützt und hat keinen Standardwert; der Zugriff darauf auf einer geladenen Instanz ergibt normalerweise None.

>>> from sqlalchemy.orm import query_expression
>>> class User(Base):
...     __tablename__ = "user_account"
...     id: Mapped[int] = mapped_column(primary_key=True)
...     name: Mapped[str]
...     fullname: Mapped[Optional[str]]
...     book_count: Mapped[int] = query_expression()
...
...     def __repr__(self) -> str:
...         return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

Mit dem in unserem Mapping konfigurierten Attribut User.book_count können wir es mit Daten aus einem SQL-Ausdruck über die Ladeoption with_expression() mit Daten aus einem SQL-Ausdruck befüllen, um jedem User-Objekt beim Laden einen benutzerdefinierten SQL-Ausdruck zuzuweisen.

>>> from sqlalchemy.orm import with_expression
>>> stmt = (
...     select(User)
...     .join_from(User, Book)
...     .group_by(Book.owner_id)
...     .options(with_expression(User.book_count, func.count(Book.id)))
... )
>>> for user in session.scalars(stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT count(book.id) AS count_1, user_account.id, user_account.name, user_account.fullname FROM user_account JOIN book ON user_account.id = book.owner_id GROUP BY book.owner_id [...] ()
Username: spongebob Number of books: 3 Username: sandy Number of books: 3

Oben haben wir unseren Ausdruck func.count(Book.id) aus dem Spaltenargument des Konstrukts select() in die Ladeoption with_expression() verschoben. Das ORM betrachtet dies dann als eine spezielle Spaltenladeoption, die dynamisch auf die Anweisung angewendet wird.

Das Mapping query_expression() hat diese Einschränkungen.

  • Auf einem Objekt, bei dem with_expression() nicht zum Befüllen des Attributs verwendet wurde, hat das Attribut einer Objektinstanz den Wert None, es sei denn, auf dem Mapping wurde der Parameter query_expression.default_expr auf einen Standard-SQL-Ausdruck gesetzt.

  • Der Wert von with_expression() wird nicht auf ein bereits geladenes Objekt angewendet, es sei denn, Populate Existing wird verwendet. Das folgende Beispiel funktioniert nicht, da das Objekt A bereits geladen ist.

    # load the first A
    obj = session.scalars(select(A).order_by(A.id)).first()
    
    # load the same A with an option; expression will **not** be applied
    # to the already-loaded object
    obj = session.scalars(select(A).options(with_expression(A.expr, some_expr))).first()

    Um sicherzustellen, dass das Attribut auf einem bestehenden Objekt neu geladen wird, verwenden Sie die Ausführungsoption Populate Existing, um sicherzustellen, dass alle Spalten neu befüllt werden.

    obj = session.scalars(
        select(A)
        .options(with_expression(A.expr, some_expr))
        .execution_options(populate_existing=True)
    ).first()
  • Der SQL-Ausdruck with_expression() geht verloren, wenn das Objekt abgelaufen ist. Sobald das Objekt abgelaufen ist, entweder über Session.expire() oder über das expire_on_commit-Verhalten von Session.commit(), ist der SQL-Ausdruck und sein Wert nicht mehr mit dem Attribut verbunden und gibt bei nachfolgendem Zugriff None zurück.

  • with_expression() wirkt als Ladeoption nur auf den äußersten Teil einer Abfrage und nur für eine Abfrage gegen eine vollständige Entität, nicht für beliebige Spaltenauswahlen, innerhalb von Unterabfragen oder Elemente einer zusammengesetzten Anweisung wie UNION. Siehe den nächsten Abschnitt Verwendung von with_expression() mit UNIONs und anderen Unterabfragen für ein Beispiel.

  • Das gemappte Attribut kann nicht auf andere Teile der Abfrage, wie die WHERE-Klausel, die ORDER BY-Klausel angewendet werden und den Ad-hoc-Ausdruck verwenden; das heißt, dies funktioniert nicht

    # can't refer to A.expr elsewhere in the query
    stmt = (
        select(A)
        .options(with_expression(A.expr, A.x + A.y))
        .filter(A.expr > 5)
        .order_by(A.expr)
    )

    Der Ausdruck A.expr wird in der obigen WHERE- und ORDER BY-Klausel zu NULL aufgelöst. Um den Ausdruck in der gesamten Abfrage zu verwenden, weisen Sie ihn einer Variablen zu und verwenden Sie diese

    # assign desired expression up front, then refer to that in
    # the query
    a_expr = A.x + A.y
    stmt = (
        select(A)
        .options(with_expression(A.expr, a_expr))
        .filter(a_expr > 5)
        .order_by(a_expr)
    )

Siehe auch

Die Option with_expression() ist eine spezielle Option, die verwendet wird, um SQL-Ausdrücke zur Abfragezeit dynamisch auf gemappte Klassen anzuwenden. Für gewöhnliche feste SQL-Ausdrücke, die auf Mappern konfiguriert sind, siehe den Abschnitt SQL-Ausdrücke als gemappte Attribute.

Verwendung von with_expression() mit UNIONs und anderen Unterabfragen

Das Konstrukt with_expression() ist eine ORM-Ladeoption und kann daher nur auf die äußerste Ebene einer SELECT-Anweisung angewendet werden, die eine bestimmte ORM-Entität laden soll. Es hat keine Auswirkung, wenn es innerhalb einer select() verwendet wird, die dann als Unterabfrage oder als Element einer zusammengesetzten Anweisung wie UNION verwendet wird.

Um beliebige SQL-Ausdrücke in Unterabfragen zu verwenden, sollten normale Core-Methoden zum Hinzufügen von Ausdrücken verwendet werden. Um einen aus einer Unterabfrage abgeleiteten Ausdruck auf die query_expression()-Attribute der ORM-Entität abzubilden, wird with_expression() auf der obersten Ebene des ORM-Objektsladens verwendet und verweist auf den SQL-Ausdruck innerhalb der Unterabfrage.

Im folgenden Beispiel werden zwei select()-Konstrukte gegen die ORM-Entität A mit einem zusätzlichen SQL-Ausdruck namens expr verwendet und mit union_all() kombiniert. Dann wird auf der obersten Ebene die Entität A aus diesem UNION ausgewählt, unter Verwendung der unter Auswählen von Entitäten aus UNIONs und anderen Mengenoperationen beschriebenen Abfragetechnik, und eine Option mit with_expression() hinzugefügt, um diesen SQL-Ausdruck auf neu geladene Instanzen von A zu extrahieren.

>>> from sqlalchemy import union_all
>>> s1 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "spongebob")
... )
>>> s2 = (
...     select(User, func.count(Book.id).label("book_count"))
...     .join_from(User, Book)
...     .where(User.name == "sandy")
... )
>>> union_stmt = union_all(s1, s2)
>>> orm_stmt = (
...     select(User)
...     .from_statement(union_stmt)
...     .options(with_expression(User.book_count, union_stmt.selected_columns.book_count))
... )
>>> for user in session.scalars(orm_stmt):
...     print(f"Username: {user.name}  Number of books: {user.book_count}")
SELECT user_account.id, user_account.name, user_account.fullname, count(book.id) AS book_count FROM user_account JOIN book ON user_account.id = book.owner_id WHERE user_account.name = ? UNION ALL SELECT user_account.id, user_account.name, user_account.fullname, count(book.id) AS book_count FROM user_account JOIN book ON user_account.id = book.owner_id WHERE user_account.name = ? [...] ('spongebob', 'sandy')
Username: spongebob Number of books: 3 Username: sandy Number of books: 3

API für Spaltenladen

Objektname Beschreibung

defer(key, *addl_attrs, [raiseload])

Gibt an, dass das gegebene spaltenorientierte Attribut verzögert werden soll, d. h. erst geladen, wenn darauf zugegriffen wird.

deferred(column, *additional_columns, [group, raiseload, comparator_factory, init, repr, default, default_factory, compare, kw_only, hash, active_history, expire_on_flush, info, doc])

Gibt ein spaltenbasiertes gemapptes Attribut an, das standardmäßig erst geladen wird, wenn darauf zugegriffen wird.

load_only(*attrs, [raiseload])

Gibt an, dass für eine bestimmte Entität nur die angegebenen spaltenbasierten Attributnamen geladen werden sollen; alle anderen werden verzögert.

query_expression([default_expr], *, [repr, compare, expire_on_flush, info, doc])

Gibt ein Attribut an, das aus einem SQL-Ausdruck zur Abfragezeit befüllt wird.

undefer(key, *addl_attrs)

Gibt an, dass das gegebene spaltenorientierte Attribut nicht verzögert werden soll, d. h. in der SELECT-Anweisung der gesamten Entität angegeben werden soll.

undefer_group(name)

Gibt an, dass Spalten innerhalb des gegebenen Gruppennamens für die Verzögerung nicht verzögert werden sollen.

with_expression(key, expression)

Wendet einen Ad-hoc-SQL-Ausdruck auf ein „deferred expression“-Attribut an.

function sqlalchemy.orm.defer(key: Literal['*'] | QueryableAttribute[Any], *addl_attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) _AbstractLoad

Gibt an, dass das gegebene spaltenorientierte Attribut verzögert werden soll, d. h. erst geladen, wenn darauf zugegriffen wird.

Diese Funktion ist Teil der Load-Schnittstelle und unterstützt sowohl die verkettete als auch die eigenständige Operation.

z. B.

from sqlalchemy.orm import defer

session.query(MyClass).options(
    defer(MyClass.attribute_one), defer(MyClass.attribute_two)
)

Um eine verzögerte Ladung eines Attributs einer zugehörigen Klasse anzugeben, kann der Pfad Token für Token angegeben werden, wobei der Ladestil für jede Verknüpfung in der Kette spezifiziert wird. Um den Ladestil für eine Verknüpfung unverändert zu lassen, verwenden Sie defaultload()

session.query(MyClass).options(
    defaultload(MyClass.someattr).defer(RelatedClass.some_column)
)

Mehrere auf Beziehungen bezogene Verzögerungsoptionen können mit Load.options() gebündelt werden.

select(MyClass).options(
    defaultload(MyClass.someattr).options(
        defer(RelatedClass.some_column),
        defer(RelatedClass.some_other_column),
        defer(RelatedClass.another_column),
    )
)
Parameter:
  • key – Zu verzögerndes Attribut.

  • raiseload – löst InvalidRequestError aus, anstatt einen Wert verzögert zu laden, wenn auf das verzögerte Attribut zugegriffen wird. Wird verwendet, um die Ausgabe von unerwünschtem SQL zu verhindern.

Neu in Version 1.4.

function sqlalchemy.orm.deferred(column: _ORMColumnExprArgument[_T], *additional_columns: _ORMColumnExprArgument[Any], group: str | None = None, raiseload: bool = False, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: Any | None = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, hash: _NoArg | bool | None = _NoArg.NO_ARG, active_history: bool = False, expire_on_flush: bool = True, info: _InfoType | None = None, doc: str | None = None) MappedSQLExpression[_T]

Gibt ein spaltenbasiertes gemapptes Attribut an, das standardmäßig erst geladen wird, wenn darauf zugegriffen wird.

Bei der Verwendung von mapped_column() wird die gleiche Funktionalität wie beim deferred()-Konstrukt durch die Verwendung des Parameters mapped_column.deferred bereitgestellt.

Parameter:
  • *columns – Spalten, die gemappt werden sollen. Dies ist typischerweise ein einzelnes Column-Objekt, aber eine Sammlung wird unterstützt, um mehrere Spalten zu unterstützen, die unter demselben Attribut gemappt sind.

  • raiseload

    boolean, wenn True, gibt an, dass eine Ausnahme ausgelöst werden soll, wenn die Ladeoperation stattfinden soll.

    Neu in Version 1.4.

Zusätzliche Argumente sind die gleichen wie bei column_property().

function sqlalchemy.orm.query_expression(default_expr: _ORMColumnExprArgument[_T] = <sqlalchemy.sql.elements.Null object>, *, repr: Union[_NoArg, bool] = _NoArg.NO_ARG, compare: Union[_NoArg, bool] = _NoArg.NO_ARG, expire_on_flush: bool = True, info: Optional[_InfoType] = None, doc: Optional[str] = None) MappedSQLExpression[_T]

Gibt ein Attribut an, das aus einem SQL-Ausdruck zur Abfragezeit befüllt wird.

Parameter:

default_expr – Optionaler SQL-Ausdruck, der in allen Fällen verwendet wird, wenn er nicht später mit with_expression() zugewiesen wird.

Neu seit Version 1.2.

Siehe auch

Laden von beliebigen SQL-Ausdrücken in Objekte - Hintergrund und Anwendungsbeispiele

function sqlalchemy.orm.load_only(*attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) _AbstractLoad

Gibt an, dass für eine bestimmte Entität nur die angegebenen spaltenbasierten Attributnamen geladen werden sollen; alle anderen werden verzögert.

Diese Funktion ist Teil der Load-Schnittstelle und unterstützt sowohl die verkettete als auch die eigenständige Operation.

Beispiel - Angenommen, eine Klasse User, laden Sie nur die Attribute name und fullname.

session.query(User).options(load_only(User.name, User.fullname))

Beispiel - Angenommen, eine Beziehung User.addresses -> Address, geben Sie Subquery-Laden für die Sammlung User.addresses an, aber laden Sie bei jedem Address-Objekt nur das Attribut email_address.

session.query(User).options(
    subqueryload(User.addresses).load_only(Address.email_address)
)

Für eine Anweisung mit mehreren Entitäten kann die führende Entität mit dem Load-Konstruktor gezielt angesprochen werden.

stmt = (
    select(User, Address)
    .join(User.addresses)
    .options(
        Load(User).load_only(User.name, User.fullname),
        Load(Address).load_only(Address.email_address),
    )
)

Wenn diese Option zusammen mit der Ausführungsoption populate_existing verwendet wird, werden nur die angegebenen Attribute aktualisiert.

Parameter:
  • *attrs – Zu ladende Attribute, alle anderen werden verzögert.

  • raiseload

    löst InvalidRequestError aus, anstatt einen Wert verzögert zu laden, wenn auf ein verzögertes Attribut zugegriffen wird. Wird verwendet, um die Ausgabe von unerwünschtem SQL zu verhindern.

    Neu in Version 2.0.

Parameter:
  • *attrs – Zu ladende Attribute, alle anderen werden verzögert.

  • raiseload

    löst InvalidRequestError aus, anstatt einen Wert verzögert zu laden, wenn auf ein verzögertes Attribut zugegriffen wird. Wird verwendet, um die Ausgabe von unerwünschtem SQL zu verhindern.

    Neu in Version 2.0.

function sqlalchemy.orm.undefer(key: Literal['*'] | QueryableAttribute[Any], *addl_attrs: Literal['*'] | QueryableAttribute[Any]) _AbstractLoad

Gibt an, dass das gegebene spaltenorientierte Attribut nicht verzögert werden soll, d. h. in der SELECT-Anweisung der gesamten Entität angegeben werden soll.

Die nicht verzögerte Spalte ist typischerweise auf der Zuordnung als deferred()-Attribut eingerichtet.

Diese Funktion ist Teil der Load-Schnittstelle und unterstützt sowohl die verkettete als auch die eigenständige Operation.

Beispiele

# undefer two columns
session.query(MyClass).options(
    undefer(MyClass.col1), undefer(MyClass.col2)
)

# undefer all columns specific to a single class using Load + *
session.query(MyClass, MyOtherClass).options(Load(MyClass).undefer("*"))

# undefer a column on a related object
select(MyClass).options(defaultload(MyClass.items).undefer(MyClass.text))
Parameter:

key – Nicht zu verzögerndes Attribut.

function sqlalchemy.orm.undefer_group(name: str) _AbstractLoad

Gibt an, dass Spalten innerhalb des gegebenen Gruppennamens für die Verzögerung nicht verzögert werden sollen.

Die nicht verzögerten Spalten sind auf der Zuordnung als deferred()-Attribute eingerichtet und enthalten einen „Gruppen“-Namen.

Z.B.

session.query(MyClass).options(undefer_group("large_attrs"))

Um eine Gruppe von Attributen einer zugehörigen Entität nicht zu verzögern, kann der Pfad über Beziehungs-Ladeoptionen wie defaultload() angegeben werden.

select(MyClass).options(
    defaultload("someattr").undefer_group("large_attrs")
)
function sqlalchemy.orm.with_expression(key: _AttrType, expression: _ColumnExpressionArgument[Any]) _AbstractLoad

Wendet einen Ad-hoc-SQL-Ausdruck auf ein „deferred expression“-Attribut an.

Diese Option wird in Verbindung mit dem Mapper-Level-Konstrukt query_expression() verwendet, das ein Attribut angibt, das das Ziel eines Ad-hoc-SQL-Ausdrucks sein soll.

Z. B.

stmt = select(SomeClass).options(
    with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y)
)

Neu seit Version 1.2.

Parameter:
  • key – Zu befüllendes Attribut

  • expr – SQL-Ausdruck, der auf das Attribut angewendet werden soll.

Siehe auch

Laden von beliebigen SQL-Ausdrücken in Objekte - Hintergrund und Anwendungsbeispiele