Функция subqueryload
Функция subqueryload включает стратегию
жадной загрузки связанных объектов. Она
загружает коллекцию или скалярную связь
отдельным подзапросом, который выполняется
сразу после основного запроса. Первым
параметром передаётся атрибут связи, который
нужно загрузить. Это избавляет от проблемы
N+1 запросов, когда доступ к связи в цикле
порождает отдельный SQL-запрос на каждую
запись.
Функцию subqueryload удобно применять
через опцию options у объекта
select. В отличие от joinedload,
который соединяет таблицы через JOIN,
subqueryload сначала выбирает родительские
строки, а затем догружает связанные данные
отдельным запросом с оператором IN.
Синтаксис
from sqlalchemy.orm import subqueryload
subqueryload(attribute)
Пример
Давайте создадим модели статьи и комментария, связанные отношением один-ко-многим:
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class Article(Base):
__tablename__ = 'articles'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
text: Mapped[str]
status: Mapped[str]
num: Mapped[int]
comments: Mapped[list['Comment']] = relationship(back_populates='article')
class Comment(Base):
__tablename__ = 'comments'
id: Mapped[int] = mapped_column(primary_key=True)
text: Mapped[str]
article_id: Mapped[int] = mapped_column(ForeignKey('articles.id'))
article: Mapped['Article'] = relationship(back_populates='comments')
Пример
Давайте заполним базу данными и загрузим
статьи вместе с комментариями через
subqueryload:
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, subqueryload
from models import Base, Article, Comment
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
article1 = Article(title='article 1', text='text 1', status='new', num=1)
article2 = Article(title='article 2', text='text 2', status='new', num=2)
article1.comments = [Comment(text='comment 1'), Comment(text='comment 2')]
article2.comments = [Comment(text='comment 3')]
session.add_all([article1, article2])
session.commit()
stmt = select(Article).options(subqueryload(Article.comments))
res = session.scalars(stmt).all()
for article in res:
print(article.title, [c.text for c in article.comments])
Результат выполнения кода:
"article 1 ['comment 1', 'comment 2']"
"article 2 ['comment 3']"
Пример
Давайте посмотрим, какие SQL-запросы
выполняет subqueryload. Основной запрос
выбирает статьи, а второй запрос догружает
комментарии через IN:
Итоговые SQL-запросы:
SELECT articles.id, articles.title, articles.text, articles.status, articles.num
FROM articles
SELECT comments.article_id AS comments_article_id, comments.id AS comments_id, comments.text AS comments_text
FROM comments
WHERE comments.article_id IN (1)
Пример
Давайте применим subqueryload для
обратной связи, загружая статью вместе с
комментариями со стороны комментария:
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, subqueryload
from models import Base, Article, Comment
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
with Session(engine) as session:
article = Article(title='article 1', text='text 1', status='new', num=1)
article.comments = [Comment(text='comment 1')]
session.add(article)
session.commit()
stmt = select(Comment).options(subqueryload(Comment.article))
res = session.scalars(stmt).all()
for comment in res:
print(comment.text, '->', comment.article.title)
Результат выполнения кода:
"comment 1 -> article 1"
Смотрите также
-
функцию
selectinload,
которая загружает связь запросом с IN -
функцию
joinedload,
которая загружает связь через JOIN -
функцию
lazyload,
которая включает ленивую загрузку связи -
функцию
relationship,
которая настраивает связь между моделями