Техники загрузки отношений¶
О данном документе
В этом разделе подробно рассматривается загрузка связанных объектов. Читатель должен быть знаком с Конфигурация отношений и основными приемами его использования.
В большинстве приведенных здесь примеров предполагается настройка отображения «Пользователь/Адрес», аналогичная той, что показана в 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 объединенная нетерпеливая загрузка проходит еще одну милю, а затем еще десять миль, чтобы гарантировать, что она не влияет на конечный результат запроса, а только на способ загрузки коллекций и связанных с ними объектов, независимо от формата запроса.
См.также
Маршрутизация явных объединений/выражений в коллекциях, загружаемых с нетерпением - использование contains_eager()
Выберите загрузку 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) )
См.также
Почему ORDER BY рекомендуется использовать с LIMIT (особенно с subqueryload())? - подробный пример
Загрузка «подзапросов» также приводит к дополнительным проблемам производительности/сложности при использовании многоуровневой загрузки, так как подзапросы будут многократно вложены друг в друга.
Загрузка «подзапросов» несовместима с «пакетной» загрузкой, обеспечиваемой 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) |
Указывает, что данный атрибут должен быть загружен с использованием «ленивой» загрузки. |
Представляет собой опции загрузчика, которые изменяют состояние поддерживающего ORM |
|
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
может быть полезнымMembers
contains_eager(), defaultload(), defer(), get_children(), immediateload(), inherit_cache, joinedload(), lazyload(), load_only(), noload(), options(), process_compile_state(), process_compile_state_replaced_entities(), propagate_to_loaders, raiseload(), selectin_polymorphic(), selectinload(), subqueryload(), undefer(), undefer_group(), with_expression()
Классная подпись
класс
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()
.
-
method
- 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))