Техники загрузки отношений

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

В этом разделе подробно рассматривается загрузка связанных объектов. Читатель должен быть знаком с Конфигурация отношений и основными приемами его использования.

В большинстве приведенных здесь примеров предполагается настройка отображения «Пользователь/Адрес», аналогичная той, что показана в setup for selects.

Важной частью SQLAlchemy является предоставление широкого диапазона контроля над тем, как связанные объекты загружаются при запросе. Под «связанными объектами» мы понимаем коллекции или скалярные ассоциации, сконфигурированные на маппере с помощью relationship(). Это поведение может быть настроено при построении маппера с помощью параметра relationship.lazy к функции relationship(), а также с помощью ORM loader options с помощью конструкции Select.

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

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

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

Краткое описание стилей загрузки отношений

Основными формами загрузки отношений являются:

  • ленивая загрузка - доступна через lazy='select' или опцию lazyload(), это форма загрузки, которая выдает оператор SELECT во время доступа к атрибутам для ленивой загрузки связанных ссылок на один объект за раз. Ленивая загрузка является стилем загрузки по умолчанию для всех конструкций relationship(), в которых не указана опция relationship.lazy. Ленивая загрузка подробно описана в Ленивая загрузка.

  • select IN loading - доступна через lazy='selectin' или опцию selectinload(), при такой форме загрузки выполняется второй (или более) оператор SELECT, который собирает идентификаторы первичных ключей родительских объектов в предложение IN, так что все члены связанных коллекций / скалярных ссылок загружаются сразу по первичному ключу. Подробно загрузка Select IN описана в Выберите загрузку IN.

  • Сопряженная загрузка - доступна через lazy='joined' или опцию joinedload(), эта форма загрузки применяет JOIN к заданному оператору SELECT, так что связанные строки загружаются в один и тот же набор результатов. Подробно об объединенной загрузке рассказывается в Присоединился к Eager Loading.

  • raise loading - доступна через lazy='raise', lazy='raise_on_sql' или опцию raiseload(), эта форма загрузки запускается в то же время, когда обычно происходит ленивая загрузка, но при этом возникает ORM-исключение, чтобы защитить приложение от нежелательных ленивых загрузок. Введение в повышение загрузки находится в Предотвращение нежелательных «ленивых» загрузок с помощью raiseload.

  • загрузка подзапроса - доступна через lazy='subquery' или опцию subqueryload(), при такой форме загрузки выполняется второй оператор SELECT, который переформулирует исходный запрос, встроенный в подзапрос, а затем соединяет этот подзапрос со связанной таблицей, чтобы загрузить сразу все члены связанных коллекций / скалярных ссылок. Подробно загрузка подзапросов описана в разделе Ускоренная загрузка подзапросов.

  • загрузка только записей - доступна через lazy='write_only' или путем аннотирования левой части объекта Relationship с помощью аннотации WriteOnlyMapped. Этот стиль загрузчика только коллекции создает альтернативный атрибутивный инструментарий, который никогда не загружает записи из базы данных неявно, а разрешает только методы WriteOnlyCollection.add(), WriteOnlyCollection.add_all() и WriteOnlyCollection.remove(). Запрос к коллекции осуществляется путем вызова оператора SELECT, который строится с помощью метода WriteOnlyCollection.select(). Загрузка только на запись рассматривается в Писать только отношения.

  • динамическая загрузка - доступна через lazy='dynamic' или путем аннотирования левой части объекта Relationship с помощью аннотации DynamicMapped. Это унаследованный стиль загрузчика только коллекций, который создает объект Query при обращении к коллекции, что позволяет использовать пользовательский SQL для работы с содержимым коллекции. Однако при различных обстоятельствах динамические загрузчики неявно выполняют итерацию базовой коллекции, что делает их менее удобными для управления действительно большими коллекциями. На смену динамическим загрузчикам приходят коллекции «write only», которые не допускают неявной загрузки базовой коллекции ни при каких обстоятельствах. Динамические загрузчики рассматриваются в разделе Динамические загрузчики отношений.

Настройка стратегий загрузчика в момент отображения

Стратегия загрузчика для конкретного отношения может быть настроена во время отображения таким образом, чтобы она действовала во всех случаях, когда загружается объект отображаемого типа, при отсутствии каких-либо модифицирующих ее опций на уровне запроса. Это настраивается с помощью параметра relationship.lazy в relationship(); обычные значения этого параметра - select, selectin и joined.

Ниже приведен пример отношения Один ко многим, настраивающий отношение Parent.children на использование Выберите загрузку IN при выдаче оператора SELECT для объектов Parent:

from typing import List

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Child"]] = relationship(lazy="selectin")


class Child(Base):
    __tablename__ = "child"

    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))

Выше, при загрузке коллекции Parent объектов, для каждого Parent также будет заполнена коллекция children, используя стратегию загрузчика "selectin", который выдает второй запрос.

По умолчанию значение аргумента relationship.lazy равно "select", что указывает на Ленивая загрузка.

Загрузка отношений с помощью опций загрузчика

Другой и, возможно, более распространенный способ настройки стратегий загрузки - это настройка их на основе отдельных запросов по определенным атрибутам с помощью метода Select.options(). Очень детальный контроль над загрузкой отношений возможен с помощью опций загрузчика, наиболее распространенными из которых являются joinedload(), selectinload() и lazyload(). Опция принимает атрибут class-bound, ссылающийся на конкретный класс/атрибут, на который должна быть направлена загрузка:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

# set children to load lazily
stmt = select(Parent).options(lazyload(Parent.children))

from sqlalchemy.orm import joinedload

# set children to load eagerly with a join
stmt = select(Parent).options(joinedload(Parent.children))

Опции загрузчика также могут быть «соединены в цепочку» с помощью метода chaining, чтобы указать, как должна происходить загрузка на более глубоких уровнях:

from sqlalchemy import select
from sqlalchemy.orm import joinedload

stmt = select(Parent).options(
    joinedload(Parent.children).subqueryload(Child.subelements)
)

Опции цепного загрузчика могут быть применены к «лениво» загруженной коллекции. Это означает, что когда коллекция или ассоциация лениво загружается при доступе, указанная опция будет действовать:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = select(Parent).options(lazyload(Parent.children).subqueryload(Child.subelements))

Выше запрос вернет Parent объектов без загруженных коллекций children. При первом обращении к коллекции children на конкретном объекте Parent будет произведена ленивая загрузка связанных с ним объектов, но дополнительно будет применена ускоренная загрузка коллекции subelements на каждом члене children.

Добавление критериев к параметрам загрузчика

Атрибуты отношения, используемые для указания опций загрузчика, включают возможность добавления дополнительных критериев фильтрации в предложение ON создаваемого соединения или в критерии WHERE, в зависимости от стратегии загрузчика. Этого можно достичь с помощью метода PropComparator.and_(), который передаст опцию таким образом, что загружаемые результаты будут ограничены заданными критериями фильтрации:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = select(A).options(lazyload(A.bs.and_(B.id > 5)))

При использовании ограничивающих критериев, если определенная коллекция уже загружена, она не будет обновлена; чтобы обеспечить выполнение нового критерия, примените опцию выполнения Заполнить существующие:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = (
    select(A)
    .options(lazyload(A.bs.and_(B.id > 5)))
    .execution_options(populate_existing=True)
)

Для того чтобы добавить критерии фильтрации ко всем вхождениям сущности во всем запросе, независимо от стратегии загрузчика или места ее появления в процессе загрузки, см. функцию with_loader_criteria().

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

Указание подвариантов с помощью Load.options()

При использовании цепочки методов стиль загрузчика каждой ссылки в пути указывается в явном виде. Для перемещения по пути без изменения существующего стиля загрузчика конкретного атрибута можно использовать метод/функцию defaultload():

from sqlalchemy import select
from sqlalchemy.orm import defaultload

stmt = select(A).options(defaultload(A.atob).joinedload(B.btoc))

Аналогичный подход можно использовать и для задания сразу нескольких подвариантов, используя метод Load.options():

from sqlalchemy import select
from sqlalchemy.orm import defaultload
from sqlalchemy.orm import joinedload

stmt = select(A).options(
    defaultload(A.atob).options(joinedload(B.btoc), joinedload(B.btod))
)

См.также

Использование load_only() на связанных объектах и коллекциях - иллюстрирует примеры комбинирования вариантов загрузчика, ориентированного на отношения и столбцы.

Примечание

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

stmt = select(Parent).options(lazyload(Parent.children).subqueryload(Child.subelements))

Если срок действия коллекции children на конкретном объекте Parent, загруженном приведенным выше запросом, истек (например, при фиксации или откате транзакции объекта Session или при использовании Session.expire_all()), то при следующем обращении к коллекции Parent.children для ее повторной загрузки коллекция Child.subelements снова будет загружена с использованием подзапроса eager loading. Это сохраняется даже в том случае, если к указанному объекту Parent обращаются из последующего запроса, в котором задан другой набор опций. Чтобы изменить опции существующего объекта без его удаления и повторной загрузки, их необходимо задать явно в сочетании с опцией выполнения Заполнить существующие:

# change the options on Parent objects that were already loaded
stmt = (
    select(Parent)
    .execution_options(populate_existing=True)
    .options(lazyload(Parent.children).lazyload(Child.subelements))
    .all()
)

Если загруженные выше объекты будут полностью очищены от Session, например, в результате сборки мусора или того, что использовался Session.expunge_all(), то «липкие» опции также исчезнут, и вновь созданные объекты при повторной загрузке будут использовать новые опции.

В будущем выпуске SQLAlchemy может появиться больше альтернатив для манипулирования опциями загрузчика на уже загруженных объектах.

Ленивая загрузка

По умолчанию все межобъектные связи имеют режим ленивой загрузки. Атрибут скаляра или коллекции, связанный с атрибутом relationship(), содержит триггер, который срабатывает при первом обращении к атрибуту. Этот триггер обычно выполняет SQL-вызов в точке доступа для загрузки связанного объекта или объектов:

>>> spongebob.addresses
{execsql}SELECT
    addresses.id AS addresses_id,
    addresses.email_address AS addresses_email_address,
    addresses.user_id AS addresses_user_id
FROM addresses
WHERE ? = addresses.user_id
[5]
{stop}[<Address(u'spongebob@google.com')>, <Address(u'j25@yahoo.com')>]

Единственный случай, когда SQL не выдается, - это простое отношение «многие-к-одному», когда связанный объект может быть идентифицирован только по первичному ключу и этот объект уже присутствует в текущем Session. По этой причине, хотя ленивая загрузка может быть дорогой для связанных коллекций, в случае, когда загружается множество объектов с простыми отношениями «многие-к-одному» против относительно небольшого набора возможных целевых объектов, ленивая загрузка может быть способна ссылаться на эти объекты локально, не выдавая столько операторов SELECT, сколько существует родительских объектов.

Такое поведение по умолчанию «загрузка при обращении к атрибуту» известно как «ленивая» или «выборочная» загрузка - название «выборочная» связано с тем, что при первом обращении к атрибуту обычно выдается оператор «SELECT».

Ленивая загрузка может быть включена для данного атрибута, который обычно конфигурируется другим способом, с помощью опции lazyload() загрузчика:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

# force lazy loading for an attribute that is set to
# load some other way normally
stmt = select(User).options(lazyload(User.addresses))

Предотвращение нежелательных «ленивых» загрузок с помощью raiseload

Стратегия lazyload() приводит к эффекту, который является одной из наиболее распространенных проблем, упоминаемых в объектно-реляционном отображении; N plus one problem, который заключается в том, что для любых N загруженных объектов обращение к их атрибутам с ленивой загрузкой означает, что будет выполнено N+1 операторов SELECT. В SQLAlchemy для решения проблемы N+1 обычно используется очень эффективная система ускоренной загрузки. Однако при этом требуется, чтобы загружаемые атрибуты были заранее указаны с помощью Select. Проблема кода, который может обращаться к другим атрибутам, не загруженным с нетерпением, когда ленивая загрузка нежелательна, может быть решена с помощью стратегии raiseload(); эта стратегия загрузчика заменяет поведение ленивой загрузки на выдачу информативной ошибки:

from sqlalchemy import select
from sqlalchemy.orm import raiseload

stmt = select(User).options(raiseload(User.addresses))

В объекте User, загруженном из приведенного выше запроса, не будет загружена коллекция .addresses; если в дальнейшем какой-либо код попытается получить доступ к этому атрибуту, то возникнет ORM-исключение.

raiseload() может использоваться с так называемым спецификатором «подстановочного знака» для указания того, что все отношения должны использовать данную стратегию. Например, чтобы настроить только один атрибут как eager loading, а все остальные как raise:

from sqlalchemy import select
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import raiseload

stmt = select(Order).options(joinedload(Order.items), raiseload("*"))

Приведенный выше подстановочный знак будет применяться ко всем отношениям не только на объектах Order и items, но и на объектах Item. Чтобы установить raiseload() только для объектов Order, укажите полный путь с помощью Load:

from sqlalchemy import select
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import Load

stmt = select(Order).options(joinedload(Order.items), Load(Order).raiseload("*"))

И наоборот, чтобы установить повышение только для объектов Item:

stmt = select(Order).options(joinedload(Order.items).raiseload("*"))

Опция raiseload() применяется только к атрибутам отношений. Для атрибутов, ориентированных на столбцы, опция defer() поддерживает опцию defer.raiseload, которая работает аналогичным образом.

Совет

Стратегии «raiseload» не применяются внутри процесса unit of work flush. Это означает, что если процессу Session.flush() необходимо загрузить коллекцию для завершения своей работы, то он сделает это в обход любых директив raiseload().

Присоединился к Eager Loading

Объединенная ускоренная загрузка - это самый старый стиль ускоренной загрузки, входящий в состав SQLAlchemy ORM. Он работает путем подключения JOIN (по умолчанию LEFT OUTER join) к оператору SELECT, и заполняет целевой скаляр/коллекцию из того же набора результатов, что и родительский.

На уровне отображения это выглядит так:

class Address(Base):
    # ...

    user: Mapped[User] = relationship(lazy="joined")

Присоединенная ускоренная загрузка обычно применяется как опция к запросу, а не как опция загрузки по умолчанию в отображении, в частности, когда используется для коллекций, а не для ссылок «многие к одному». Для этого используется опция загрузчика joinedload():

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import joinedload
>>> stmt = select(User).options(joinedload(User.addresses)).filter_by(name="spongebob")
>>> spongebob = session.scalars(stmt).unique().all()
{execsql}SELECT
    addresses_1.id AS addresses_1_id,
    addresses_1.email_address AS addresses_1_email_address,
    addresses_1.user_id AS addresses_1_user_id,
    users.id AS users_id, users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
LEFT OUTER JOIN addresses AS addresses_1
    ON users.id = addresses_1.user_id
WHERE users.name = ?
['spongebob']

Совет

При включении joinedload() в ссылку на коллекцию типа «один ко многим» или «многие ко многим» к возвращаемому результату должен быть применен метод Result.unique(), который унифицирует входящие строки по первичному ключу, которые в противном случае будут размножены в результате объединения. При отсутствии этого метода ORM выдаст ошибку.

Это не является автоматическим в современной SQLAlchemy, поскольку изменяет поведение набора результатов, возвращая меньшее количество ORM-объектов, чем обычно возвращает оператор по количеству строк. Поэтому SQLAlchemy использует Result.unique() явно, чтобы не было двусмысленности в том, что возвращаемые объекты унифицируются по первичному ключу.

По умолчанию используется LEFT OUTER JOIN, что позволяет использовать ведущий объект, который не ссылается на связанную строку. Для атрибута, который гарантированно имеет элемент, например, для ссылки «многие-к-одному» на связанный объект, где внешний ключ ссылки NOT NULL, запрос может быть более эффективным за счет использования внутреннего соединения; это доступно на уровне отображения с помощью флага relationship.innerjoin:

class Address(Base):
    # ...

    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
    user: Mapped[User] = relationship(lazy="joined", innerjoin=True)

На уровне опций запроса, с помощью флага joinedload.innerjoin:

from sqlalchemy import select
from sqlalchemy.orm import joinedload

stmt = select(Address).options(joinedload(Address.user, innerjoin=True))

При применении JOIN в цепочке, включающей OUTER JOIN, JOIN будет вложен в правую часть:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import joinedload
>>> stmt = select(User).options(
...     joinedload(User.addresses).joinedload(Address.widgets, innerjoin=True)
... )
>>> results = session.scalars(stmt).unique().all()
{execsql}SELECT
    widgets_1.id AS widgets_1_id,
    widgets_1.name AS widgets_1_name,
    addresses_1.id AS addresses_1_id,
    addresses_1.email_address AS addresses_1_email_address,
    addresses_1.user_id AS addresses_1_user_id,
    users.id AS users_id, users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
LEFT OUTER JOIN (
    addresses AS addresses_1 JOIN widgets AS widgets_1 ON
    addresses_1.widget_id = widgets_1.id
) ON users.id = addresses_1.user_id

Совет

Если при выполнении SELECT используется техника блокировки строк базы данных, то есть метод Select.with_for_update() используется для выполнения SELECT..FOR UPDATE, то в зависимости от поведения используемого бэкенда может быть заблокирована и объединенная таблица. По этой причине не рекомендуется использовать объединенную ускоренную загрузку одновременно с SELECT..FOR UPDATE.

Дзен присоединенной загрузки

Поскольку объединенная ускоренная загрузка имеет много общего с использованием Select.join(), она часто приводит к путанице в вопросе о том, когда и как ее следует использовать. Важно понимать, что если Select.join() используется для изменения результатов запроса, то joinedload() делает все возможное, чтобы не изменить результаты запроса, а вместо этого скрыть эффекты визуализированного соединения, чтобы только обеспечить присутствие связанных объектов.

Философия стратегий загрузчиков заключается в том, что к конкретному запросу может быть применен любой набор схем загрузки, и результаты не изменятся - изменится только количество SQL-операторов, необходимых для полной загрузки связанных объектов и коллекций. Вначале конкретный запрос может использовать все ленивые загрузки. После его использования в контексте может выясниться, что к определенным атрибутам или коллекциям обращаются постоянно, и что эффективнее было бы изменить стратегию загрузчика для них. Стратегия может быть изменена без каких-либо других модификаций запроса, результаты останутся идентичными, но будет выдано меньшее количество SQL-запросов. Теоретически (и практически) ничто, что можно сделать с Select, не заставит его загрузить другой набор первичных или связанных объектов на основе изменения стратегии загрузчика.

В частности, joinedload() достигает этого результата, не оказывая никакого влияния на возвращаемые строки сущности, тем, что создает анонимный псевдоним для соединений, добавляемых в запрос, так что на них не могут ссылаться другие части запроса. Например, в приведенном ниже запросе используется joinedload() для создания LEFT OUTER JOIN из users в addresses, однако ORDER BY, добавленный к Address.email_address, не является действительным - сущность Address не названа в запросе:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import joinedload
>>> stmt = (
...     select(User)
...     .options(joinedload(User.addresses))
...     .filter(User.name == "spongebob")
...     .order_by(Address.email_address)
... )
>>> result = session.scalars(stmt).unique().all()
{execsql}SELECT
    addresses_1.id AS addresses_1_id,
    addresses_1.email_address AS addresses_1_email_address,
    addresses_1.user_id AS addresses_1_user_id,
    users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
LEFT OUTER JOIN addresses AS addresses_1
    ON users.id = addresses_1.user_id
WHERE users.name = ?
ORDER BY addresses.email_address   <-- this part is wrong !
['spongebob']

Выше, ORDER BY addresses.email_address не является корректным, так как addresses отсутствует в списке FROM. Правильным способом загрузки записей User и упорядочивания по адресу электронной почты является использование Select.join():

>>> from sqlalchemy import select
>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .filter(User.name == "spongebob")
...     .order_by(Address.email_address)
... )
>>> result = session.scalars(stmt).unique().all()
{execsql}
SELECT
    users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
JOIN addresses ON users.id = addresses.user_id
WHERE users.name = ?
ORDER BY addresses.email_address
['spongebob']

Приведенное выше утверждение, конечно, отличается от предыдущего тем, что столбцы из addresses вообще не включаются в результат. Мы можем добавить joinedload() обратно, чтобы было два джойна - один тот, по которому мы упорядочиваем, а второй используется анонимно для загрузки содержимого коллекции User.addresses:

>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .options(joinedload(User.addresses))
...     .filter(User.name == "spongebob")
...     .order_by(Address.email_address)
... )
>>> result = session.scalars(stmt).unique().all()
{execsql}SELECT
    addresses_1.id AS addresses_1_id,
    addresses_1.email_address AS addresses_1_email_address,
    addresses_1.user_id AS addresses_1_user_id,
    users.id AS users_id, users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users JOIN addresses
    ON users.id = addresses.user_id
LEFT OUTER JOIN addresses AS addresses_1
    ON users.id = addresses_1.user_id
WHERE users.name = ?
ORDER BY addresses.email_address
['spongebob']

Мы видим, что использование Select.join() заключается в том, чтобы снабдить JOIN-клаузулами, которые мы хотели бы использовать в последующих критериях запроса, в то время как использование joinedload() связано только с загрузкой коллекции User.addresses для каждого User в результате. В этом случае два джойна, скорее всего, кажутся избыточными, что и происходит. Если бы мы хотели использовать только один JOIN как для загрузки коллекции, так и для упорядочивания, мы бы использовали вариант contains_eager(), описанный ниже в Маршрутизация явных объединений/выражений в коллекциях, загружаемых с нетерпением. Но чтобы понять, почему joinedload() делает то, что делает, рассмотрим, если бы мы фильтровали на конкретном Address:

>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .options(joinedload(User.addresses))
...     .filter(User.name == "spongebob")
...     .filter(Address.email_address == "someaddress@foo.com")
... )
>>> result = session.scalars(stmt).unique().all()
{execsql}SELECT
    addresses_1.id AS addresses_1_id,
    addresses_1.email_address AS addresses_1_email_address,
    addresses_1.user_id AS addresses_1_user_id,
    users.id AS users_id, users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users JOIN addresses
    ON users.id = addresses.user_id
LEFT OUTER JOIN addresses AS addresses_1
    ON users.id = addresses_1.user_id
WHERE users.name = ? AND addresses.email_address = ?
['spongebob', 'someaddress@foo.com']

Выше мы видим, что эти два JOIN выполняют совершенно разные функции. Один будет соответствовать ровно одной строке - соединению User и Address, где Address.email_address=='someaddress@foo.com'. Другой LEFT OUTER JOIN будет соответствовать всем строкам Address, связанным с User, и будет использоваться только для заполнения коллекции User.addresses для тех объектов User, которые будут возвращены.

Изменив использование joinedload() на другой стиль загрузки, мы можем изменить способ загрузки коллекции совершенно независимо от SQL, используемого для извлечения нужных нам строк User. Ниже мы изменим joinedload() на selectinload():

>>> stmt = (
...     select(User)
...     .join(User.addresses)
...     .options(selectinload(User.addresses))
...     .filter(User.name == "spongebob")
...     .filter(Address.email_address == "someaddress@foo.com")
... )
>>> result = session.scalars(stmt).all()
{execsql}SELECT
    users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
JOIN addresses ON users.id = addresses.user_id
WHERE
    users.name = ?
    AND addresses.email_address = ?
['spongebob', 'someaddress@foo.com']
# ... selectinload() emits a SELECT in order
# to load all address records ...

Если в запросе содержится модификатор, влияющий на возвращаемые строки, например, при использовании DISTINCT, LIMIT, OFFSET и т.п., то готовый запрос сначала оборачивается в подзапрос, а к подзапросу применяются соединения, используемые специально для загрузки с нетерпением. В SQLAlchemy объединенная нетерпеливая загрузка проходит еще одну милю, а затем еще десять миль, чтобы гарантировать, что она не влияет на конечный результат запроса, а только на способ загрузки коллекций и связанных с ними объектов, независимо от формата запроса.

Выберите загрузку IN

В большинстве случаев загрузка с помощью селектинов является наиболее простым и эффективным способом ускоренной загрузки коллекций объектов. Единственный сценарий, при котором ускоренная загрузка с помощью selectin невозможна, - это когда в модели используются составные первичные ключи, а внутренняя база данных не поддерживает кортежи с IN, к которым в настоящее время относится SQL Server.

Загрузка «Select IN» осуществляется с помощью аргумента "selectin" к relationship.lazy или с помощью опции загрузчика selectinload(). При таком стиле загрузки для загрузки связанных ассоциаций используется SELECT, который ссылается на значения первичного ключа родительского объекта или, в случае отношения «многие-к-одному», на значения дочерних объектов внутри предложения IN:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import selectinload
>>> stmt = (
...     select(User)
...     .options(selectinload(User.addresses))
...     .filter(or_(User.name == "spongebob", User.name == "ed"))
... )
>>> result = session.scalars(stmt).all()
{execsql}SELECT
    users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
WHERE users.name = ? OR users.name = ?
('spongebob', 'ed')
SELECT
    addresses.id AS addresses_id,
    addresses.email_address AS addresses_email_address,
    addresses.user_id AS addresses_user_id
FROM addresses
WHERE addresses.user_id IN (?, ?)
(5, 7)

Выше второй SELECT ссылается на addresses.user_id IN (5, 7), где «5» и «7» являются значениями первичных ключей для двух предыдущих загруженных объектов User; после полной загрузки партии объектов их значения первичных ключей подставляются в предложение IN для второго SELECT. Поскольку отношение между User и Address имеет простое условие первичного соединения и предусматривает, что значения первичного ключа для User могут быть получены из Address.user_id, в операторе вообще нет соединений или подзапросов.

Для простых загрузок «многие-к-одному» JOIN также не нужен, поскольку используется значение внешнего ключа из родительского объекта:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import selectinload
>>> stmt = select(Address).options(selectinload(Address.user))
>>> result = session.scalars(stmt).all()
{execsql}SELECT
    addresses.id AS addresses_id,
    addresses.email_address AS addresses_email_address,
    addresses.user_id AS addresses_user_id
    FROM addresses
SELECT
    users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
WHERE users.id IN (?, ?)
(1, 2)

Совет

Под «простым» подразумевается, что условие relationship.primaryjoin выражает сравнение на равенство между первичным ключом стороны «один» и прямым внешним ключом стороны «много», без каких-либо дополнительных критериев.

Загрузка Select IN также поддерживает отношения «многие-ко-многим», где в настоящее время для сопоставления строк из одной стороны в другую используется JOIN по всем трем таблицам.

О таком виде загрузки следует знать следующее:

  • Стратегия выдает SELECT для 500 значений родительского первичного ключа одновременно, поскольку первичные ключи преобразуются в большое выражение IN в операторе SQL. Некоторые базы данных, например Oracle, имеют жесткое ограничение на размер IN-выражения, и в целом размер SQL-строки не должен быть произвольно большим.

  • Поскольку загрузка «selectin» опирается на IN, для отображения с составными первичными ключами необходимо использовать «кортежную» форму IN, которая выглядит как WHERE (table.column_a, table.column_b) IN ((?, ?), (?, ?), (?, ?)). Такой синтаксис в настоящее время не поддерживается на SQL Server, а для SQLite требуется версия не ниже 3.15. В SQLAlchemy нет специальной логики для предварительной проверки того, какие платформы поддерживают этот синтаксис, и если база данных будет запущена на неподдерживающей платформе, то она немедленно выдаст ошибку. Преимущество SQLAlchemy в том, что если какая-то база данных действительно начнет поддерживать этот синтаксис, то она будет работать без каких-либо изменений в SQLAlchemy (как это было в случае с SQLite).

Ускоренная загрузка подзапросов

Legacy Feature

Стратегия subqueryload(), использующая метод ускоренной загрузки, на данный момент является в основном устаревшей и вытеснена стратегией selectinload(), которая имеет более простую конструкцию, более гибкие возможности, такие как Yield Per, и в большинстве случаев выдает более эффективные SQL-запросы. Поскольку стратегия subqueryload() основана на переинтерпретации исходного оператора SELECT, она может работать неэффективно при очень сложных исходных запросах.

subqueryload() может оставаться полезным для конкретного случая, когда коллекция загружается с нетерпением для объектов, использующих составные первичные ключи, на бэкенде Microsoft SQL Server, который по-прежнему не поддерживает синтаксис «tuple IN».

Загрузка подзапросов по принципу действия аналогична загрузке selectin eager, однако выдаваемый оператор SELECT является производным от исходного оператора и имеет более сложную структуру запроса, чем при загрузке selectin eager.

Ускоренная загрузка подзапросов осуществляется с помощью аргумента "subquery" в relationship.lazy или с помощью опции загрузчика subqueryload().

Работа подзапроса eager loading заключается в том, что для каждого отношения, подлежащего загрузке, выдается второй оператор SELECT, причем сразу для всех объектов-результатов. Этот оператор SELECT ссылается на исходный оператор SELECT, обернутый внутри подзапроса, так что мы получаем тот же список первичных ключей для возвращаемого первичного объекта, а затем связываем его с суммой всех членов коллекции, чтобы загрузить их одновременно:

>>> from sqlalchemy import select
>>> from sqlalchemy.orm import subqueryload
>>> stmt = select(User).options(subqueryload(User.addresses)).filter_by(name="spongebob")
>>> results = session.scalars(stmt).all()
{execsql}SELECT
    users.id AS users_id,
    users.name AS users_name,
    users.fullname AS users_fullname,
    users.nickname AS users_nickname
FROM users
WHERE users.name = ?
('spongebob',)
SELECT
    addresses.id AS addresses_id,
    addresses.email_address AS addresses_email_address,
    addresses.user_id AS addresses_user_id,
    anon_1.users_id AS anon_1_users_id
FROM (
    SELECT users.id AS users_id
    FROM users
    WHERE users.name = ?) AS anon_1
JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id, addresses.id
('spongebob',)

О таком виде загрузки следует знать следующее:

  • Оператор SELECT, выдаваемый стратегией загрузчика «subquery», в отличие от «selectin», требует подзапроса и наследует все ограничения производительности, имеющиеся в исходном запросе. Сам подзапрос также может нести потери производительности в зависимости от особенностей используемой базы данных.

  • Загрузка «подзапросов» накладывает особые требования к их упорядочиванию для корректной работы. Запрос, использующий subqueryload() в сочетании с ограничивающим модификатором, таким как Select.limit() или Select.offset(), должен всегда включать Select.order_by() против уникального столбца (столбцов), такого как первичный ключ, чтобы дополнительные запросы, выдаваемые subqueryload(), включали то же упорядочивание, которое используется в родительском запросе. Без этого существует вероятность того, что внутренний запрос вернет не те строки:

    # incorrect, no ORDER BY
    stmt = select(User).options(subqueryload(User.addresses).limit(1))
    
    # incorrect if User.name is not unique
    stmt = select(User).options(subqueryload(User.addresses)).order_by(User.name).limit(1)
    
    # correct
    stmt = (
        select(User)
        .options(subqueryload(User.addresses))
        .order_by(User.name, User.id)
        .limit(1)
    )
  • Загрузка «подзапросов» также приводит к дополнительным проблемам производительности/сложности при использовании многоуровневой загрузки, так как подзапросы будут многократно вложены друг в друга.

  • Загрузка «подзапросов» несовместима с «пакетной» загрузкой, обеспечиваемой Yield Per, как для коллекций, так и для скалярных отношений.

По указанным причинам стратегия «selectin» должна быть предпочтительнее, чем «subquery».

Какой тип загрузки использовать?

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

Коллекция «один ко многим» / «многие ко многим « - Стратегия selectinload(), как правило, является наилучшей для загрузки. Она выдает дополнительный SELECT, который использует как можно меньше таблиц, не затрагивая исходный оператор, и является наиболее гибкой для любого типа исходного запроса. Единственным существенным ограничением является использование таблицы с составными первичными ключами на бэкенде, не поддерживающем «tuple IN», к которым в настоящее время относятся SQL Server и очень старые версии SQLite; все остальные включенные бэкенды его поддерживают.

Many to One - стратегия joinedload() является наиболее универсальной. В особых случаях, при очень малом количестве потенциально связанных значений, может быть полезна стратегия immediateload(), так как эта стратегия будет извлекать объект из локального Session, не выдавая никакого SQL, если связанный объект уже присутствует.

Полиморфная ускоренная загрузка

Поддерживается задание полиморфных опций для каждой отдельной нагрузки. Примеры использования метода PropComparator.of_type() в сочетании с функцией with_polymorphic() см. в разделе Повышенная загрузка полиморфных подтипов.

Стратегии загрузки с использованием диких символов

Каждая из опций joinedload(), subqueryload(), lazyload(), selectinload(), noload() и raiseload() может быть использована для установки стиля загрузки relationship() по умолчанию для конкретного запроса, затрагивая все relationship() -сопоставленные атрибуты, не указанные иначе в операторе. Эта возможность доступна при передаче строки '*' в качестве аргумента любой из этих опций:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

stmt = select(MyClass).options(lazyload("*"))

Вышеуказанная опция lazyload('*') заменяет собой установку lazy для всех конструкций relationship(), используемых для данного запроса, за исключением тех, в которых используются lazy='write_only' или lazy='dynamic'.

Если в некоторых отношениях указано, например, lazy='joined' или lazy='selectin', то использование lazyload('*') в одностороннем порядке заставит все эти отношения использовать загрузку 'select', например, выдавать оператор SELECT при обращении к каждому атрибуту.

Эта опция не отменяет опции загрузчика, указанные в запросе, такие как joinedload(), selectinload() и т.д. В приведенном ниже запросе для отношения widget по-прежнему будет использоваться объединенная загрузка:

from sqlalchemy import select
from sqlalchemy.orm import lazyload
from sqlalchemy.orm import joinedload

stmt = select(MyClass).options(lazyload("*"), joinedload(MyClass.widget))

В то время как приведенная выше инструкция для joinedload() будет выполняться независимо от того, находится ли она до или после опции lazyload(), если было передано несколько опций, каждая из которых включала "*", то в действие вступит последняя из них.

Стратегии загрузки подстановочных символов на каждый объект

Вариантом стратегии загрузчика с подстановочным знаком является возможность задавать стратегию для каждого объекта. Например, при запросе на User и Address мы можем предписать всем отношениям на Address использовать ленивую загрузку, оставив при этом незатронутыми стратегии загрузчика для User, применив сначала объект Load, а затем указав * в качестве цепочечной опции:

from sqlalchemy import select
from sqlalchemy.orm import Load

stmt = select(User, Address).options(Load(Address).lazyload("*"))

Выше, все отношения на Address будут установлены на ленивую загрузку.

Маршрутизация явных объединений/выражений в коллекциях, загружаемых с нетерпением

Поведение joinedload() таково, что джойны создаются автоматически, используя в качестве целей анонимные псевдонимы, результаты которых направляются в коллекции и скалярные ссылки на загруженные объекты. Часто бывает так, что запрос уже содержит необходимые джойны, представляющие конкретную коллекцию или скалярную ссылку, а джойны, добавленные функцией joinedload, оказываются лишними - тем не менее, хотелось бы, чтобы коллекции/ссылки были заполнены.

Для этого SQLAlchemy предоставляет опцию contains_eager(). Эта опция используется так же, как и опция joinedload(), за исключением того, что предполагается, что объект Select будет явно включать соответствующие соединения, обычно с помощью методов типа Select.join(). Ниже мы указываем соединение между User и Address и дополнительно устанавливаем его в качестве основы для ускоренной загрузки User.addresses:

from sqlalchemy.orm import contains_eager

stmt = select(User).join(User.addresses).options(contains_eager(User.addresses))

Если часть оператора «eager» является «aliased», то путь должен быть указан с помощью PropComparator.of_type(), что позволяет передать конкретную конструкцию aliased():

# use an alias of the Address entity
adalias = aliased(Address)

# construct a statement which expects the "addresses" results

stmt = (
    select(User)
    .outerjoin(User.addresses.of_type(adalias))
    .options(contains_eager(User.addresses.of_type(adalias)))
)

# get results normally
r = session.scalars(stmt).unique().all()
{execsql}SELECT
    users.user_id AS users_user_id,
    users.user_name AS users_user_name,
    adalias.address_id AS adalias_address_id,
    adalias.user_id AS adalias_user_id,
    adalias.email_address AS adalias_email_address,
    (...other columns...)
FROM users
LEFT OUTER JOIN email_addresses AS email_addresses_1
ON users.user_id = email_addresses_1.user_id

Путь, указанный в качестве аргумента contains_eager(), должен быть полным путем от начальной сущности. Например, если бы мы загружали Users->orders->Order->items->Item, то в качестве аргумента использовался бы вариант:

stmt = select(User).options(contains_eager(User.orders).contains_eager(Order.items))

Использование функции contains_eager() для загрузки результатов коллекции с пользовательской фильтрацией

Когда мы используем contains_eager(), мы конструируем для себя SQL, который будет использоваться для заполнения коллекций. Отсюда естественно следует, что мы можем по своему усмотрению модифицировать значения, которые должна хранить коллекция, написав наш SQL так, чтобы загружать подмножество элементов для коллекций или скалярных атрибутов.

В качестве примера можно привести загрузку объекта User и нетерпеливую загрузку в его коллекцию .addresses только определенных адресов путем фильтрации объединенных данных, маршрутизации их с помощью contains_eager(), а также использования Заполнить существующие для обеспечения перезаписи всех уже загруженных коллекций:

stmt = (
    select(User)
    .join(User.addresses)
    .filter(Address.email_address.like("%@aol.com"))
    .options(contains_eager(User.addresses))
    .execution_options(populate_existing=True)
)

Приведенный выше запрос загрузит только те объекты User, которые содержат хотя бы Address объект, содержащий подстроку 'aol.com' в своем поле email; коллекция User.addresses будет содержать только эти записи Address и не любые другие записи Address, которые на самом деле связаны с этой коллекцией.

Совет

Во всех случаях SQLAlchemy ORM не перезаписывает уже загруженные атрибуты и коллекции, если на это нет указания. Поскольку используется identity map, часто бывает так, что ORM-запрос возвращает объекты, которые на самом деле уже присутствовали и были загружены в память. Поэтому при использовании contains_eager() для заполнения коллекции альтернативным способом обычно целесообразно использовать Заполнить существующие, как показано выше, чтобы уже загруженная коллекция была обновлена новыми данными. Опция populate_existing сбрасывает все атрибуты, которые уже присутствовали в коллекции, включая ожидающие изменения, поэтому перед ее использованием убедитесь, что все данные сброшены. Достаточно использовать Session с его поведением по умолчанию autoflush.

Примечание

Настроенная коллекция, которую мы загружаем с помощью contains_eager(), не является «липкой», т.е. при следующей загрузке этой коллекции она будет загружена с обычным содержимым по умолчанию. Коллекция может быть перезагружена в случае истечения срока действия объекта, что происходит при использовании методов Session.commit(), Session.rollback(), предполагающих стандартные настройки сессии, или при использовании методов Session.expire_all() или Session.expire().

API загрузчика отношений

Object Name Description

contains_eager(*keys, **kw)

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

defaultload(*keys)

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

immediateload(*keys, [recursion_depth])

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

joinedload(*keys, **kw)

Указывает, что данный атрибут должен быть загружен с использованием объединенной ускоренной загрузки.

lazyload(*keys)

Указывает, что данный атрибут должен быть загружен с использованием «ленивой» загрузки.

Load

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

noload(*keys)

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

raiseload(*keys, **kw)

Укажите, что при обращении к данному атрибуту должна возникнуть ошибка.

selectinload(*keys, [recursion_depth])

Указывает, что данный атрибут должен быть загружен с помощью SELECT IN eager loading.

subqueryload(*keys)

Указывает, что данный атрибут должен быть загружен с помощью подзапроса eager loading.

function sqlalchemy.orm.contains_eager(*keys: Union[str, QueryableAttribute[Any]], **kw: Any) _AbstractLoad

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

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

Опция используется в сочетании с явным объединением, которое загружает нужные строки, т.е.:

sess.query(Order).\
        join(Order.user).\
        options(contains_eager(Order.user))

Приведенный выше запрос соединит сущность Order с соответствующей ей сущностью User, а в возвращаемых объектах Order будет предварительно заполнен атрибут Order.user.

Он также может быть использован для настройки записей в нетерпеливо загружаемой коллекции; обычно запросы хотят использовать опцию выполнения Заполнить существующие, предполагая, что первичная коллекция родительских объектов может быть уже загружена:

sess.query(User).\
    join(User.addresses).\
    filter(Address.email_address.like('%@aol.com')).\
    options(contains_eager(User.addresses)).\
    populate_existing()

Более подробная информация об использовании приведена в разделе Маршрутизация явных объединений/выражений в коллекциях, загружаемых с нетерпением.

function sqlalchemy.orm.defaultload(*keys: Union[str, QueryableAttribute[Any]]) _AbstractLoad

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

Этот метод используется для связи с другими опциями загрузчика, расположенными дальше в цепочке атрибутов, без изменения стиля загрузчика звеньев этой цепочки. Например, для установки подключенной ускоренной загрузки для элемента element:

session.query(MyClass).options(
    defaultload(MyClass.someattribute).
    joinedload(MyOtherClass.someotherattribute)
)

defaultload() также полезен для установки опций на уровне столбцов в родственном классе, а именно в классах defer() и undefer():

session.query(MyClass).options(
    defaultload(MyClass.someattribute).
    defer("some_column").
    undefer("some_other_column")
)
function sqlalchemy.orm.immediateload(*keys: Union[str, QueryableAttribute[Any]], recursion_depth: Optional[int] = None) _AbstractLoad

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

Загрузка осуществляется с использованием стратегии «lazyloader» и не вызывает дополнительных загрузчиков eager.

Опция immediateload() в общем случае заменяется опцией selectinload(), которая выполняет ту же задачу более эффективно, выдавая SELECT для всех загруженных объектов.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

Параметры:

recursion_depth – необязательное значение int; при установке в положительное целое число в сочетании с самореферентным отношением указывает на то, что загрузка «селектина» будет автоматически продолжаться на столько уровней вглубь, пока не будет найдено ни одного элемента. … примечание:: Опция immediateload.recursion_depth в настоящее время поддерживает только самореферентные отношения. Пока не существует возможности автоматического обхода рекурсивных структур, в которых задействовано более одного отношения. … warning:: Этот параметр является новым и экспериментальным и должен рассматриваться как статус «альфа» .. versionadded:: 2.0 добавлена immediateload.recursion_depth.

function sqlalchemy.orm.joinedload(*keys: Union[str, QueryableAttribute[Any]], **kw: Any) _AbstractLoad

Указывает, что данный атрибут должен быть загружен с использованием объединенной ускоренной загрузки.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

примеры:

# joined-load the "orders" collection on "User"
query(User).options(joinedload(User.orders))

# joined-load Order.items and then Item.keywords
query(Order).options(
    joinedload(Order.items).joinedload(Item.keywords))

# lazily load Order.items, but when Items are loaded,
# joined-load the keywords collection
query(Order).options(
    lazyload(Order.items).joinedload(Item.keywords))
Параметры:

innerjoin – Если True, то это указывает на то, что в объединенной eager-загрузке следует использовать внутреннее соединение вместо стандартного левого внешнего соединения:: query(Order).options(joinedload(Order.user, innerjoin=True))

Для того, чтобы связать между собой несколько простых соединений, одни из которых могут быть OUTER, а другие INNER, используются право-вложенные соединения:

query(A).options(
    joinedload(A.bs, innerjoin=False).
        joinedload(B.cs, innerjoin=True)
)

В приведенном выше запросе, связывающем A.bs с помощью «внешнего» соединения и B.cs с помощью «внутреннего» соединения, соединения будут выглядеть как «a LEFT OUTER JOIN (b JOIN c)». При использовании старых версий SQLite (< 3.7.16) эта форма JOIN транслируется для использования полных подзапросов, так как в других случаях этот синтаксис напрямую не поддерживается.

Флаг innerjoin также может быть указан с помощью выражения "unnested". Это указывает на то, что следует использовать INNER JOIN, если присоединение не связано с LEFT OUTER JOIN слева, в этом случае оно будет отображаться как LEFT OUTER JOIN. Например, предположим, что A.bs является внешним соединением:

query(A).options(
    joinedload(A.bs).
        joinedload(B.cs, innerjoin="unnested")
)

Приведенное выше соединение будет выглядеть как «a LEFT OUTER JOIN b LEFT OUTER JOIN c», а не как «a LEFT OUTER JOIN (b JOIN c)».

Примечание

Флаг «unnested» не влияет на JOIN, выводимый из таблицы объединения «многие-ко-многим», например, таблицы, сконфигурированной как relationship.secondary, в целевую таблицу; для корректности результатов такие объединения всегда являются INNER и, следовательно, являются право-вложенными, если связаны с OUTER-соединением.

Примечание

Соединения, создаваемые joinedload(), являются анонимно алиасированными. Критерии, по которым происходит объединение, не могут быть изменены, равно как и ORM-совместимые Select или унаследованные Query не могут ссылаться на эти объединения каким-либо образом, включая упорядочивание. Более подробно см. раздел Дзен присоединенной загрузки.

Для получения конкретного SQL JOIN, доступного в явном виде, используйте Select.join() и Query.join(). Для комбинирования явных JOIN с нетерпеливой загрузкой коллекций используйте contains_eager(); см. Маршрутизация явных объединений/выражений в коллекциях, загружаемых с нетерпением.

function sqlalchemy.orm.lazyload(*keys: Union[str, QueryableAttribute[Any]]) _AbstractLoad

Указывает, что данный атрибут должен быть загружен с использованием «ленивой» загрузки.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

class sqlalchemy.orm.Load

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

Объект Load в большинстве случаев используется неявно, за кадром, при использовании опций запроса типа joinedload(), defer() и т.п. Как правило, он не инстанцируется напрямую, за исключением некоторых специфических случаев.

См.также

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

Классная подпись

класс sqlalchemy.orm.Load (sqlalchemy.orm.strategy_options._AbstractLoad)

method sqlalchemy.orm.Load.contains_eager(attr: _AttrType, alias: Optional[_FromClauseArgument] = None, _is_chain: bool = False) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.contains_eager метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией contains_eager().

Примеры использования см. в разделе contains_eager().

method sqlalchemy.orm.Load.defaultload(attr: Union[str, QueryableAttribute[Any]]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.defaultload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией defaultload().

Примеры использования см. в разделе defaultload().

method sqlalchemy.orm.Load.defer(key: Union[str, QueryableAttribute[Any]], raiseload: bool = False) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.defer метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией defer().

Примеры использования см. в разделе defer().

method sqlalchemy.orm.Load.get_children(*, omit_attrs: Tuple[str, ...] = (), **kw: Any) Iterable[HasTraverseInternals]

наследуется от HasTraverseInternals.get_children() метода HasTraverseInternals

Возвращает непосредственные дочерние HasTraverseInternals элементы данного HasTraverseInternals.

Это используется для обхода посещений.

**kw может содержать флаги, изменяющие возвращаемую коллекцию, например, для возврата подмножества элементов, чтобы сократить объем обхода, или для возврата дочерних элементов из другого контекста (например, коллекции на уровне схемы вместо коллекции на уровне клаузы).

method sqlalchemy.orm.Load.immediateload(attr: Union[str, QueryableAttribute[Any]], recursion_depth: Optional[int] = None) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.immediateload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией immediateload().

Примеры использования см. в разделе immediateload().

attribute sqlalchemy.orm.Load.inherit_cache: Optional[bool] = None

наследуется от HasCacheKey.inherit_cache атрибута HasCacheKey

Укажите, должен ли данный экземпляр HasCacheKey использовать схему генерации ключей кэша, применяемую его непосредственным суперклассом.

По умолчанию атрибут принимает значение None, что указывает на то, что конструкция еще не приняла во внимание целесообразность участия в кэшировании; функционально это эквивалентно установке значения False, за исключением того, что при этом выдается предупреждение.

Этот флаг может быть установлен в значение True на конкретном классе, если SQL, соответствующий объекту, не изменяется на основе атрибутов, локальных для данного класса, а не его суперкласса.

См.также

Включение поддержки кэширования для пользовательских конструкций - Общие направляющие для установки атрибута HasCacheKey.inherit_cache для SQL-конструкций сторонних производителей или определяемых пользователем.

method sqlalchemy.orm.Load.joinedload(attr: Union[str, QueryableAttribute[Any]], innerjoin: Optional[bool] = None) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.joinedload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией joinedload().

Примеры использования см. в разделе joinedload().

method sqlalchemy.orm.Load.lazyload(attr: Union[str, QueryableAttribute[Any]]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.lazyload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией lazyload().

Примеры использования см. в разделе lazyload().

method sqlalchemy.orm.Load.load_only(*attrs: Union[str, QueryableAttribute[Any]], raiseload: bool = False) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.load_only метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией load_only().

Примеры использования см. в разделе load_only().

method sqlalchemy.orm.Load.noload(attr: Union[str, QueryableAttribute[Any]]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.noload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией noload().

Примеры использования см. в разделе noload().

method sqlalchemy.orm.Load.options(*opts: _AbstractLoad) Self

Применить ряд опций в качестве вложенных опций к данному объекту Load.

Например:

query = session.query(Author)
query = query.options(
            joinedload(Author.book).options(
                load_only(Book.summary, Book.excerpt),
                joinedload(Book.citations).options(
                    joinedload(Citation.author)
                )
            )
        )
Параметры:

*opts – Серия объектов опций загрузчика (в конечном итоге Load объектов), которые должны быть применены к пути, указанному данным Load объектом.

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

method sqlalchemy.orm.Load.process_compile_state(compile_state: ORMCompileState) None

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.process_compile_state метода sqlalchemy.orm.strategy_options._AbstractLoad

Применить модификацию к заданному ORMCompileState.

Этот метод является частью реализации конкретного CompileStateOption и вызывается только при компиляции ORM-запроса.

method sqlalchemy.orm.Load.process_compile_state_replaced_entities(compile_state: ORMCompileState, mapper_entities: Sequence[_MapperEntity]) None

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.process_compile_state_replaced_entities метода sqlalchemy.orm.strategy_options._AbstractLoad

Применить модификацию к заданному ORMCompileState, учитывая сущности, которые были заменены с помощью функции with_only_columns() или with_entities().

Этот метод является частью реализации конкретного CompileStateOption и вызывается только при компиляции ORM-запроса.

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

attribute sqlalchemy.orm.Load.propagate_to_loaders: bool

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.propagate_to_loaders атрибута sqlalchemy.orm.strategy_options._AbstractLoad

если True, то указывает, что эта опция должна переноситься на «вторичные» операторы SELECT, которые возникают для ленивых загрузчиков отношений, а также для операций загрузки/обновления атрибутов.

method sqlalchemy.orm.Load.raiseload(attr: Union[str, QueryableAttribute[Any]], sql_only: bool = False) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.raiseload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией raiseload().

Примеры использования см. в разделе raiseload().

method sqlalchemy.orm.Load.selectin_polymorphic(classes: Iterable[Type[Any]]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.selectin_polymorphic метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией selectin_polymorphic().

Примеры использования см. в разделе selectin_polymorphic().

method sqlalchemy.orm.Load.selectinload(attr: Union[str, QueryableAttribute[Any]], recursion_depth: Optional[int] = None) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.selectinload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией selectinload().

Примеры использования см. в разделе selectinload().

method sqlalchemy.orm.Load.subqueryload(attr: Union[str, QueryableAttribute[Any]]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.subqueryload метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией subqueryload().

Примеры использования см. в разделе subqueryload().

method sqlalchemy.orm.Load.undefer(key: Union[str, QueryableAttribute[Any]]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.undefer метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией undefer().

Примеры использования см. в разделе undefer().

method sqlalchemy.orm.Load.undefer_group(name: str) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.undefer_group метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией undefer_group().

Примеры использования см. в разделе undefer_group().

method sqlalchemy.orm.Load.with_expression(key: _AttrType, expression: _ColumnExpressionArgument[Any]) Self

наследуется от sqlalchemy.orm.strategy_options._AbstractLoad.with_expression метода sqlalchemy.orm.strategy_options._AbstractLoad

Создать новый объект Load с примененной опцией with_expression().

Примеры использования см. в разделе with_expression().

function sqlalchemy.orm.noload(*keys: Union[str, QueryableAttribute[Any]]) _AbstractLoad

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

Атрибут отношения будет возвращать None при обращении к нему, не производя никакого эффекта загрузки.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

noload() применяется только к атрибутам relationship().

Примечание

Установка этой стратегии загрузки в качестве стратегии по умолчанию для отношения, использующего параметр relationship.lazy, может привести к проблемам с промывкой, например, если при операции удаления требуется загрузить связанные объекты, а вместо этого возвращается None.

function sqlalchemy.orm.raiseload(*keys: Union[str, QueryableAttribute[Any]], **kw: Any) _AbstractLoad

Укажите, что при обращении к данному атрибуту должна возникнуть ошибка.

Атрибут отношения, настроенный на raiseload(), при обращении к нему будет выдавать сообщение InvalidRequestError. Обычно это полезно, когда приложение пытается убедиться, что все атрибуты отношений, к которым обращаются в определенном контексте, уже были загружены с помощью ускоренной загрузки. Вместо того чтобы читать журналы SQL, чтобы убедиться, что ленивые загрузки не происходят, эта стратегия приведет к тому, что они будут немедленно вызваны.

raiseload() применяется только к атрибутам relationship(). Для того чтобы применить поведение raise-on-SQL к атрибуту, основанному на столбце, используйте параметр defer.raiseload в опции загрузчика defer().

Параметры:

sql_only – Если значение True, то сообщение поднимается только в том случае, если при ленивой загрузке будет выдан SQL, но не в том случае, если она проверяет только карту идентичности или определяет, что связанное значение должно быть просто None из-за отсутствия ключей. Если False, то стратегия будет выдавать сообщение для всех типов загрузки отношений.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

function sqlalchemy.orm.selectinload(*keys: Union[str, QueryableAttribute[Any]], recursion_depth: Optional[int] = None) _AbstractLoad

Указывает, что данный атрибут должен быть загружен с помощью SELECT IN eager loading.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

примеры:

# selectin-load the "orders" collection on "User"
query(User).options(selectinload(User.orders))

# selectin-load Order.items and then Item.keywords
query(Order).options(
    selectinload(Order.items).selectinload(Item.keywords))

# lazily load Order.items, but when Items are loaded,
# selectin-load the keywords collection
query(Order).options(
    lazyload(Order.items).selectinload(Item.keywords))
Параметры:

recursion_depth – необязательное значение int; при установке в положительное целое число в сочетании с самореферентным отношением указывает на то, что загрузка «селектина» будет автоматически продолжаться на столько уровней вглубь, пока не будет найдено ни одного элемента. … примечание:: Опция selectinload.recursion_depth в настоящее время поддерживает только самореферентные отношения. Возможность автоматического обхода рекурсивных структур, в которых задействовано более одного отношения, пока отсутствует. Кроме того, параметр selectinload.recursion_depth является новым и экспериментальным и должен рассматриваться как «альфа» статус для серии 2.0. … versionadded:: 2.0 добавлен selectinload.recursion_depth.

function sqlalchemy.orm.subqueryload(*keys: Union[str, QueryableAttribute[Any]]) _AbstractLoad

Указывает, что данный атрибут должен быть загружен с помощью подзапроса eager loading.

Эта функция является частью интерфейса Load и поддерживает как цепочку методов, так и автономную работу.

примеры:

# subquery-load the "orders" collection on "User"
query(User).options(subqueryload(User.orders))

# subquery-load Order.items and then Item.keywords
query(Order).options(
    subqueryload(Order.items).subqueryload(Item.keywords))

# lazily load Order.items, but when Items are loaded,
# subquery-load the keywords collection
query(Order).options(
    lazyload(Order.items).subqueryload(Item.keywords))
Вернуться на верх