SqlAlchemy-2-0-中文文档-九-

SqlAlchemy 2.0 中文文档(九)

原文:docs.sqlalchemy.org/en/20/contents.html

关系加载技术

原文:docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html

关于本文档

本节详细介绍了如何加载相关对象。读者应熟悉关系配置和基本用法。

大多数示例假定“用户/地址”映射设置类似于在选择设置中所示的设置。

SQLAlchemy 的一个重要部分是在查询时提供对相关对象加载方式的广泛控制。所谓“相关对象”是指在映射器上使用relationship()配置的集合或标量关联。这种行为可以在映射器构造时使用relationship()函数的relationship.lazy参数进行配置,以及通过使用ORM 加载选项Select构造函数一起使用。

关系加载分为三类:延迟加载、急切加载和加载。延迟加载指的是从查询返回的对象,相关对象一开始并未加载。当在特定对象上首次访问给定集合或引用时,会发出额外的 SELECT 语句,以加载请求的集合。

急切加载是指从查询返回的对象中,相关集合或标量引用已经提前加载。ORM 实现这一点要么通过增加通常会发出的 SELECT 语句以加载相关行,要么通过在主要语句之后发出额外的 SELECT 语句以一次性加载集合或标量引用。

“无”加载指的是在给定关系上禁用加载,要么属性为空且从不加载,要么在访问时引发错误,以防止不必要的延迟加载。

关系加载样式总结

关系加载的主要形式包括:

  • 惰性加载 - 可通过lazy='select'lazyload()选项使用,这是一种加载形式,它在属性访问时发出 SELECT 语句,以惰性加载单个对象上的相关引用。惰性加载是所有未明示relationship.lazy选项的relationship()构造的默认加载方式。惰性加载详见惰性加载。

  • 选择 IN 加载 - 可通过lazy='selectin'selectinload()选项使用,这种加载形式会发出第二个(或更多)SELECT 语句,将父对象的主键标识符组装到一个 IN 子句中,以便通过主键一次加载所有相关集合/标量引用。选择 IN 加载详见选择 IN 加载。

  • 关联加载 - 可通过lazy='joined'joinedload()选项使用,这种加载方式会在给定的 SELECT 语句上应用 JOIN,以便相关行在同一结果集中加载。关联的及时加载详见关联的及时加载。

  • 引发加载 - 可通过lazy='raise'lazy='raise_on_sql'raiseload()选项使用,这种加载形式在通常会发生惰性加载的时候被触发,但它会引发一个 ORM 异常,以防止应用程序进行不必要的惰性加载。引发加载的简介详见使用 raiseload 防止不想要的惰性加载。

  • 子查询加载 - 可通过lazy='subquery'subqueryload()选项使用,这种加载方式会发出第二个 SELECT 语句,该语句重新陈述了原始查询嵌入到子查询中,然后将该子查询与要加载的相关表 JOIN 起来,以一次加载所有相关集合/标量引用的成员。子查询的及时加载详见子查询的及时加载。

  • 仅写入加载 - 可通过lazy='write_only'获得,或通过在Relationship对象的左侧使用WriteOnlyMapped注解来标注。这种仅限集合的加载方式产生了一种替代的属性装载机制,从不隐式地从数据库加载记录,而是仅允许使用WriteOnlyCollection.add()WriteOnlyCollection.add_all()WriteOnlyCollection.remove()方法。查询集合是通过调用使用WriteOnlyCollection.select()方法构建的 SELECT 语句来执行的。仅写入加载在仅写入关系中进行了讨论。

  • 动态加载 - 可通过lazy='dynamic'获得,或通过在Relationship对象的左侧使用DynamicMapped注解来标注。这是一种传统的仅限集合的加载样式,当访问集合时会产生一个Query对象,允许针对集合的内容发出自定义 SQL。然而,动态加载器在各种情况下将隐式迭代底层集合,这使它们对管理真正大型集合不太有用。动态加载器已被“仅写入”集合取代,后者将阻止在任何情况下隐式加载底层集合。动态加载器在动态关系加载器中进行了讨论。

在映射时配置加载策略

特定关系的加载策略可以在映射时配置,以在加载映射类型的对象的所有情况下发生,即使没有修改它的任何查询级别选项。这是使用relationship()relationship.lazy参数进行配置的;此参数的常见值包括selectselectinjoined

下面的示例说明了在一对多关系模式下的关系示例,配置了Parent.children关系以在发出Parent对象的 SELECT 语句时使用选择 IN 加载:

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()。该选项接受一个类绑定的属性,指示应针对特定类/属性进行定位:

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))

加载器选项也可以使用方法链接进行“链接”,以指定进一步深层次的加载方式:

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))

在上面的查询中,将返回未加载children集合的Parent对象。当首次访问特定Parent对象上的children集合时,它将延迟加载相关对象,并且还将对每个children成员的subelements集合应用急加载。

向加载器选项添加条件

用于指示加载器选项的关系属性包括向创建的连接的 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))

如果上述查询加载的特定 Parent 对象上的 children 集合过期(例如当 Session 对象的事务提交或回滚时,或使用了 Session.expire_all()),当下次访问 Parent.children 集合以重新加载时,Child.subelements 集合将再次使用子查询急加载。即使上述 Parent 对象是从指定了不同选项集的后续查询中访问的,这种情况仍然存在。要在不清除对象并重新加载的情况下更改现有对象上的选项,必须使用 Populate Existing 执行选项显式设置它们:

# 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
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]
[<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 加一问题,它指出对于任何加载的 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()用于表示所有关系都应该使用这种策略。例如,设置仅一个属性为急加载,并将其余全部设置为 raise:

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

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

上述通配符将适用于所有关系,而不仅适用于items,还适用于Item对象上的所有关系。要仅为Order对象设置raiseload(),请指定具有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”策略在工作单元刷新过程中不适用。这意味着如果Session.flush()过程需要加载集合以完成其工作,则会在绕过任何raiseload()指令的情况下执行此操作。

另请参见

通配符加载策略

使用 raiseload 防止延迟列加载 ## 连接预加载

连接预加载是包含在 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()
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()的使用是显式的,因此返回的对象是在主键上唯一的,没有任何歧义。

默认情况下发出的 JOIN 是 LEFT OUTER JOIN,以允许不引用相关行的主对象。对于保证具有元素的属性,例如引用相关对象的多对一引用,其中引用外键不为 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))

当应用于包含 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()
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()来创建从usersaddresses的 LEFT OUTER JOIN,但是针对Address.email_address添加的ORDER BY是无效的 - 查询中未命名实体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()
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()
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()
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()的用法只涉及加载结果中每个UserUser.addresses集合。在这种情况下,这两个连接很可能看起来是多余的 - 它们就是。如果我们只想使用一个 JOIN 来加载集合并进行排序,我们可以使用contains_eager()选项,下面将介绍如何将显式的 JOIN/语句路由到急加载的集合中。但要了解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()
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 的作用非常不同。一个将完全匹配一个行,即UserAddress的连接,其中Address.email_address=='someaddress@foo.com'。另一个 LEFT OUTER JOIN 将匹配与User相关的所有Address行,并且仅用于为返回的那些User对象填充User.addresses集合。

通过改变 joinedload() 的使用方式到另一种加载样式,我们可以完全独立于用于检索实际想要的 User 行的 SQL 改变集合的加载方式。以下我们将 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()
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 加载

在大多数情况下,选择 IN 加载是急加载对象集合的最简单和最有效的方式。唯一一个不可行的情况是当模型使用复合主键,并且后端数据库不支持具有 IN 的元组时,这目前包括 SQL Server。

使用 "selectin" 参数提供了“选择 IN”急加载,或者通过使用 selectinload() 加载器选项。这种加载样式发出一个 SELECT,该 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()
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 对象的主键值;在一批对象完全加载后,它们的主键值被注入到第二个 SELECT 的 IN 子句中。因为 UserAddress 之间的关系有一个简单的主键连接条件,并且提供了 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()
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 个父主键值,因为这些主键值会被渲染成 SQL 语句中的一个大型 IN 表达式。一些数据库,比如 Oracle,在 IN 表达式的大小上有一个硬限制,总体上 SQL 字符串的大小不应该是任意大的。

  • 由于“selectin”加载依赖于 IN,在具有复合主键的映射中,它必须使用“元组”形式的 IN,看起来像 WHERE (table.column_a, table.column_b) IN ((?, ?), (?, ?), (?, ?))。这种语法目前不受 SQL Server 支持,对于 SQLite 需要至少版本 3.15。SQLAlchemy 中没有特殊逻辑来提前检查哪些平台支持这种语法,如果运行在不支持的平台上,数据库将立即返回错误。SQLAlchemy 只需运行 SQL 语句以使其失败的一个优点是,如果某个特定数据库开始支持这种语法,它将无需对 SQLAlchemy 进行任何更改即可工作(就像 SQLite 的情况一样)。## 子查询急加载

传统特性

subqueryload() 预加载器在这一点上主要是传统的,已被 selectinload() 策略取代,后者设计更简单,更灵活,具有诸如 Yield Per 等功能,并在大多数情况下发出更有效的 SQL 语句。由于 subqueryload() 依赖于重新解释原始的 SELECT 语句,当给定非常复杂的源查询时,可能无法有效地工作。

对于使用复合主键的对象的急加载集合的特定情况,subqueryload() 可能仍然有用,因为 Microsoft SQL Server 后端仍然不支持“元组 IN”语法。

子查询加载在操作上类似于 selectin 急加载,但发出的 SELECT 语句是从原始语句派生的,并且具有更复杂的查询结构,类似于 selectin 急加载。

通过在 relationship.lazy 中使用 "subquery" 参数或使用 subqueryload() 加载器选项提供子查询急加载。

子查询急加载的操作是为要加载的每个关系发出第二个 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()
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 语句(与“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)
    )
    

    另请参阅

    为什么建议在 LIMIT 中使用 ORDER BY(特别是在 subqueryload() 中)? - 详细示例

  • 当在许多级别的深层急加载上使用时,“子查询”加载还会带来额外的性能/复杂性问题,因为子查询将被重复嵌套。

  • “子查询”加载与由 Yield Per 提供的“批量”加载(对集合和标量关系均适用)不兼容。

出于上述原因,“selectin”策略应优先于“子查询”。

另请参阅

选择 IN 加载 ## 何种加载方式?

使用哪种加载方式通常涉及到优化 SQL 执行次数、所发出 SQL 的复杂度以及获取的数据量之间的权衡。

一对多 / 多对多集合 - selectinload() 通常是最佳的加载策略。它会发出额外的 SELECT 查询,尽可能使用少量的表,不影响原始语句,并且对于任何类型的原始查询都非常灵活。它唯一的主要限制是在使用不支持“tuple IN”的后端的复合主键表时,目前包括 SQL Server 和非常旧的 SQLite 版本;所有其他包含的后端都支持它。

多对一 - joinedload() 策略是最通用的策略。在特殊情况下,如果潜在相关值数量很少,也可以使用immediateload() 策略,因为如果相关对象已经存在,则该策略将从本地Session获取对象,而不发出任何 SQL。

多态急加载

支持按急加载基础上的每个急加载选项进行多态选项的指定。参见 Eager Loading of Polymorphic Subtypes 部分中与with_polymorphic()函数配合使用的PropComparator.of_type()方法的示例。

通配符加载策略

joinedload()subqueryload()lazyload()selectinload()noload()raiseload() 中的每一个都可以用于为特定查询设置relationship()加载的默认样式,影响除了在语句中另有规定的所有 relationship() -映射属性。通过将字符串 '*' 作为这些选项中的任何一个的参数传递,可以使用此功能:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

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

在上面的例子中,lazyload('*') 选项将取代该查询中所有正在使用的所有 relationship() 构造的 lazy 设置,但不包括那些使用 lazy='write_only'lazy='dynamic' 的构造。

如果某些关系指定了lazy='joined'lazy='selectin',例如,使用lazyload('*')将单方面地导致所有这些关系使用'select'加载,例如,当访问每个属性时发出 SELECT 语句。

该选项不会取代查询中声明的加载选项,例如joinedload()selectinload()等。下面的查询仍将使用widget关系的 joined 加载:

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

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

尽管joinedload()的指令会发生,无论它出现在lazyload()选项之前还是之后,但如果传递了每个包含"*"的多个选项,则最后一个将生效。

按实体的通配符加载策略

通配符加载策略的变体是能够根据每个实体设置策略的能力。例如,如果查询UserAddress,我们可以指示Address上的所有关系使用延迟加载,同时通过首先应用Load对象,然后指定*作为链接选项,保持对User的加载策略不受影响:

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()等方法。下面,我们指定了UserAddress之间的连接,并将其另外建立为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()
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 来加载集合或标量属性的子集。

提示

SQLAlchemy 现在有一种更简单的方法来做到这一点,它允许将 WHERE 条件直接添加到加载器选项中,例如joinedload()selectinload() ,使用PropComparator.and_()。参见添加条件到加载器选项部分的示例。

此处描述的技术仍然适用于使用 SQL 条件或修饰符查询相关集合,而不仅仅是简单的 WHERE 子句。

例如,我们可以加载一个 User 对象,并通过过滤连接数据来将只特定地址急切地加载到其 .addresses 集合中,使用contains_eager() 路由,还使用 Populate Existing 确保任何已加载的集合都被覆盖:

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

上述查询将仅加载包含至少一个在其 email 字段中包含子字符串'aol.com'Address 对象的 User 对象;User.addresses 集合将仅包含这些 Address 条目,并且不包含实际与集合关联的任何其他 Address 条目。

提示

在所有情况下,SQLAlchemy ORM 不会覆盖已加载的属性和集合,除非有指示要这样做。由于正在使用一个身份映射,通常情况下,ORM 查询返回的对象实际上已经存在并加载到内存中。因此,当使用contains_eager()以另一种方式填充集合时,通常最好像上面示例中所示那样使用填充现有,以便已加载的集合使用新数据进行刷新。populate_existing 选项将重置已经存在的所有属性,包括待处理的更改,因此在使用它之前确保所有数据都已刷新。使用带有其默认行为的Session,默认行为为自动刷新,已足够。

注意

我们使用contains_eager()加载的定制集合不是“粘性”的;也就是说,下次加载此集合时,它将使用其通常的默认内容加载。如果对象过期,则该集合可能会重新加载,这在默认会话设置下发生,即每当使用 Session.commit()Session.rollback() 方法时,或者使用 Session.expire_all()Session.expire() 方法。

参见

将条件添加到加载器选项 - 现代 API 允许在任何关系加载器选项中直接添加 WHERE 条件

关系加载器 API

对象名称 描述
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 急加载加载。
subqueryload(*keys) 表示给定属性应该使用子查询急加载加载。
function sqlalchemy.orm.contains_eager(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) → _AbstractLoad

表示应该从查询中手动声明的列急加载给定属性。

此函数是Load接口的一部分,支持方法链和独立操作。

此选项与加载所需行的显式连接一起使用,即:

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

上述查询将从Order实体连接到其相关的User实体,并且返回的Order对象将预先填充Order.user属性。

它还可用于自定义急加载集合中的条目;查询通常会希望使用 Populate Existing 执行选项,假设父对象的主要集合可能已经被加载:

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: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

表示属性应该使用其预定义的加载器样式加载。

此加载选项的行为是不更改属性的当前加载样式,这意味着将使用先前配置的样式,或者如果没有选择先前的样式,则将使用默认加载。

此方法用于进一步链接到属性链中的其他加载器选项,而不更改沿链的链接的加载器样式。例如,要为元素的元素设置连接的急加载:

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

defaultload() 也对于在相关类上设置列级选项很有用,即defer()undefer()的选项:

session.scalars(
    select(MyClass).options(
        defaultload(MyClass.someattribute)
        .defer("some_column")
        .undefer("some_other_column")
    )
)

另请参见

使用 Load.options()指定子选项

Load.options()

function sqlalchemy.orm.immediateload(*keys: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → _AbstractLoad

表示给定属性应使用具有每个属性 SELECT 语句的立即加载进行加载。

加载是使用“lazyloader”策略实现的,并且不会触发任何其他急加载器。

immediateload() 选项一般被selectinload() 选项取代,后者通过为所有加载的对象发出一个 SELECT 更有效地执行相同的任务。

此函数是Load接口的一部分,支持方法链接和独立操作。

参数:

递归深度

可选的整数;当与自引用关系一起设置为正整数时,表示“选择加载”将自动继续到没有找到项目为止的那么多级别深度。

注意

immediateload.recursion_depth 选项当前仅支持自引用关系。目前还没有选项可以自动遍历具有多个涉及的关系的递归结构。

警告

此参数是新的实验性参数,应视为“alpha”状态

新版 2.0 中新增 immediateload.recursion_depth

另请参阅

关联加载技术

选择 IN 加载

function sqlalchemy.orm.joinedload(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) → _AbstractLoad

表示给定属性应使用连接式急加载进行加载。

此函数是Load接口的一部分,支持方法链接和独立操作。

例子:

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

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

# lazily load Order.items, but when Items are loaded,
# joined-load the keywords collection
select(Order).options(
    lazyload(Order.items).joinedload(Item.keywords)
)

参数:

innerjoin

如果为 True,则表示连接式急加载应使用内部连接而不是默认的左外连接:

select(Order).options(joinedload(Order.user, innerjoin=True))

为了将多个急加载连接在一起,其中一些可能是 OUTER 而其他是 INNER,使用右嵌套连接将它们链接起来:

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

上述查询,通过“outer”连接链接 A.bs 和通过“inner”连接链接 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 是一个 outerjoin:

select(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”标志不会影响从多对多关联表(例如,配置为relationship.secondary的表)到目标表的 JOIN;为了结果的正确性,这些 JOIN 始终是 INNER JOIN,因此如果与 OUTER JOIN 相关联,则为右嵌套。

注意

joinedload()生成的 JOIN 是匿名别名的。 JOIN 进行的标准无法修改,也无法通过 ORM 启用的Select或传统的Query以任何方式引用这些 JOIN,包括排序。有关详细信息,请参阅关联及时加载的禅意。

要生成明确可用的特定 SQL JOIN,请使用Select.join()Query.join()。要将明确的 JOIN 与集合的及时加载结合使用,请使用contains_eager();参见将明确的 JOIN/语句路由到及时加载的集合中。

另请参阅

关系加载技术

关联及时加载

function sqlalchemy.orm.lazyload(*keys: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

表明给定的属性应该使用“懒加载”。

此函数是Load接口的一部分,支持方法链接和独立操作。

另请参阅

关系加载技术

懒加载

class sqlalchemy.orm.Load

代表修改 ORM 启用的Select或传统的Query状态以影响加载各种映射属性的加载器选项。

当使用查询选项如joinedload()defer()或类似选项时,Load对象在大多数情况下会在幕后隐式使用。除了一些非常特殊的情况外,通常不会直接实例化它。

另请参阅

每个实体通配符加载策略 - 演示了直接使用Load可能有用的一个示例

成员

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 contains_eager(attr: _AttrType, alias: _FromClauseArgument | None = None, _is_chain: bool = False) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.contains_eager 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个应用了contains_eager()选项的新Load对象。

有关用法示例,请参见contains_eager()

method defaultload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.defaultload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个应用了defaultload()选项的新Load对象。

有关用法示例,请参见defaultload()

method defer(key: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.defer 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个应用了defer()选项的新Load对象。

有关用法示例,请参见defer()

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

继承自 HasTraverseInternals.get_children() 方法的 HasTraverseInternals

返回此HasTraverseInternals的直接子HasTraverseInternals元素。

用于访问遍历。

**kw 可能包含更改返回集合的标志,例如返回子集以减少更大的遍历,或者返回来自不同上下文的子项(例如模式级集合而不是子句级集合)。

method immediateload(attr: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.immediateload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用immediateload()选项应用于生成新的Load对象。

查看immediateload()以获取用法示例。

attribute inherit_cache: bool | None = None

继承自 HasCacheKeyHasCacheKey.inherit_cache 属性

指示此HasCacheKey实例是否应使用其直接超类使用的缓存键生成方案。

该属性默认为None,表示构造尚未考虑是否适合参与缓存;这在功能上等同于将值设置为False,只是还会发出警告。

如果与对象对应的 SQL 不基于本类的本地属性而是基于其超类,则可以在特定类上将此标志设置为True

另请参阅

为自定义构造启用缓存支持 - 为第三方或用户定义的 SQL 构造设置HasCacheKey.inherit_cache属性的一般指南。

method joinedload(attr: Literal['*'] | QueryableAttribute[Any], innerjoin: bool | None = None) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.joinedload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用joinedload()选项应用于生成新的Load对象。

查看joinedload()以获取用法示例。

method lazyload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.lazyload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用lazyload()选项应用于生成新的Load对象。

查看lazyload()以获取用法示例。

method load_only(*attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.load_only 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用已应用了 load_only() 选项的新 Load 对象。

有关使用示例,请参见 load_only()

method noload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.noload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

使用已应用了 noload() 选项的新 Load 对象。

有关使用示例,请参见 noload()

method 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 新增。

另请参阅

defaultload()

使用 Load.options() 指定子选项

method process_compile_state(compile_state: ORMCompileState) → None

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.process_compile_state 方法 sqlalchemy.orm.strategy_options._AbstractLoad

对给定的 ORMCompileState 应用修改。

此方法是特定 CompileStateOption 实现的一部分,并且仅在编译 ORM 查询时内部调用。

method 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 propagate_to_loaders: bool

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.propagate_to_loaders 的属性 sqlalchemy.orm.strategy_options._AbstractLoad

如果为 True,则指示此选项应该随着“次要”SELECT 语句一起传递,这些语句会出现在关系惰性加载器以及属性加载/刷新操作中。

method raiseload(attr: Literal['*'] | QueryableAttribute[Any], sql_only: bool = False) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.raiseload 方法 sqlalchemy.orm.strategy_options._AbstractLoad

使用已应用了 raiseload() 选项的新 Load 对象。

有关使用示例,请参见 raiseload()

method 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 selectinload(attr: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.selectinload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用 Load 对象,并应用 selectinload() 选项。

查看 selectinload() 以获取使用示例。

method subqueryload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.subqueryload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用 Load 对象,并应用 subqueryload() 选项。

查看 subqueryload() 以获取使用示例。

method undefer(key: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.undefer 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用 Load 对象,并应用 undefer() 选项。

查看 undefer() 以获取使用示例。

method undefer_group(name: str) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.undefer_group 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用 Load 对象,并应用 undefer_group() 选项。

查看 undefer_group() 以获取使用示例。

method 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: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

指示给定的关系属性应保持未加载状态。

当访问关系属性而不产生任何加载效果时,关系属性将返回 None

此函数是 Load 接口的一部分,并支持方法链式和独立操作。

noload() 仅适用于 relationship() 属性。

注意

将此加载策略设置为使用 relationship.lazy 参数的默认策略可能会导致刷新时出现问题,比如删除操作需要加载相关对象,而返回的却是 None

另请参阅

关系加载技术

function sqlalchemy.orm.raiseload(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) → _AbstractLoad

表示如果访问将引发错误的给定属性。

使用 raiseload() 配置的关系属性在访问时会引发 InvalidRequestError。此方法通常有用的方式是,当应用程序试图确保在特定上下文中访问的所有关系属性都已通过激进加载加载时。而不是必须阅读 SQL 日志以确保不发生懒加载,此策略将立即导致它们引发。

raiseload() 仅适用于 relationship() 属性。要将 raise-on-SQL 行为应用于基于列的属性,请在 defer() 加载选项的 defer.raiseload 参数上使用。

参数:

sql_only – 如果为 True,则仅在懒加载将发出 SQL 时引发,但如果仅检查标识映射或确定由于缺少键而相关值应为 None,则不会引发。当为 False 时,该策略将引发所有类型的关系加载。

此函数是 Load 接口的一部分,并支持方法链和独立操作。

另请参阅

关系加载技术

使用 raiseload 避免不必要的懒加载

使用 raiseload 避免延迟列加载

function sqlalchemy.orm.selectinload(*keys: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → _AbstractLoad

表示给定属性应使用 SELECT IN 激进加载。

此函数是 Load 接口的一部分,并支持方法链和独立操作。

示例:

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

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

# lazily load Order.items, but when Items are loaded,
# selectin-load the keywords collection
select(Order).options(
    lazyload(Order.items).selectinload(Item.keywords)
)

参数:

recursion_depth

可选整数;与自引用关系结合设置为正整数时,指示“选择加载”将自动继续加载到没有找到项目为止的那么多级。

注意

selectinload.recursion_depth 选项目前仅支持自引用关系。目前还没有自动遍历涉及多个关系的递归结构的选项。

此外,selectinload.recursion_depth 参数是新的实验性参数,应该在 2.0 系列中视为“alpha”状态。

2.0 版本新增:添加了selectinload.recursion_depth

另请参阅

关系加载技术

选择 IN 加载

function sqlalchemy.orm.subqueryload(*keys: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

表示应使用子查询急加载加载给定属性。

此函数是Load接口的一部分,支持方法链接和独立操作。

示例:

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

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

# lazily load Order.items, but when Items are loaded,
# subquery-load the keywords collection
select(Order).options(
    lazyload(Order.items).subqueryload(Item.keywords)
)

另请参阅

关系加载技术

子查询急加载

关系加载样式总结

关系加载的主要形式包括:

  • 延迟加载 - 通过lazy='select'lazyload() 选项可用,这是在属性访问时发出 SELECT 语句以延迟加载单个对象上的相关引用的加载形式。延迟加载是所有未指示relationship.lazy 选项的所有relationship() 构造的默认加载样式。延迟加载详细信息请参阅延迟加载。

  • 选择 IN 加载 - 通过lazy='selectin'selectinload() 选项可用,此加载形式发出第二个(或更多)SELECT 语句,将父对象的主键标识符组装成一个 IN 子句,以便通过主键一次加载所有相关集合/标量引用的成员。选择 IN 加载详细信息请参阅选择 IN 加载。

  • 连接加载 - 通过lazy='joined'joinedload() 选项可用,此加载形式将 JOIN 应用于给定的 SELECT 语句,以便相关行在同一结果集中加载。连接急加载详细信息请参阅连接急加载。

  • 抛出加载 - 可通过lazy='raise'lazy='raise_on_sql'raiseload()选项使用,这种加载方式在通常发生惰性加载的同时触发,但会引发 ORM 异常,以防止应用程序进行不必要的惰性加载。关于抛出加载的介绍请参阅使用 raiseload 防止不必要的惰性加载。

  • 子查询加载 - 可通过lazy='subquery'subqueryload()选项使用,这种加载方式会发出第二个 SELECT 语句,该语句重新陈述原始查询嵌入到子查询中,然后将该子查询与相关表进行 JOIN,以一次加载所有相关集合/标量引用的成员。子查询急切加载的详细信息请参阅子查询急切加载。

  • 仅写加载 - 可通过lazy='write_only'使用,或者通过使用Relationship对象的左侧进行注释,并使用WriteOnlyMapped注解。这种仅限集合加载样式会产生一种替代的属性仪器,从不从数据库隐式加载记录,而是仅允许WriteOnlyCollection.add()WriteOnlyCollection.add_all()WriteOnlyCollection.remove()方法。通过调用使用WriteOnlyCollection.select()方法构造的 SELECT 语句来查询集合。关于仅写加载的讨论请参阅仅写关系。

  • 动态加载 - 通过lazy='dynamic'可用,或者通过使用DynamicMapped注释来注释Relationship对象的左侧。这是一种传统的仅集合加载器样式,当访问集合时会生成一个Query对象,允许针对集合内容发出自定义 SQL。然而,动态加载器在各种情况下会隐式迭代底层集合,这使得它们对于管理真正大型集合不太有用。动态加载器被“仅写入”集合取代,这将阻止在任何情况下隐式加载底层集合。动态加载器在动态关系加载器中讨论。

在映射时间配置加载策略

特定关系的加载策略可以在映射时间配置,以在加载映射类型的对象的所有情况下发生,没有任何修改它的查询级选项的情况下。这是使用relationship()relationship.lazy参数进行配置的;该参数的常见值包括selectselectinjoined

下面的示例说明了在一对多关系示例中,配置Parent.children关系以在发出Parent对象的 SELECT 语句时使用 Select IN loading:

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()。该选项接受一个类绑定的属性,引用应该被定位的特定类/属性:

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))

加载器选项也可以使用 方法链 进行“链接”,以指定加载应如何进行更深层次的操作:

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 集合。当首次访问特定 Parent 对象上的 children 集合时,它将惰性加载相关对象,但还将对 children 中的每个成员的 subelements 集合应用急切加载。

向加载器选项添加条件

用于指示加载器选项的关系属性包括向创建的联接的 ON 子句或涉及的 WHERE 条件添加额外的筛选条件的能力,具体取决于加载器策略。这可以通过使用 PropComparator.and_() 方法来实现,该方法将通过一个选项,使加载的结果限制为给定的筛选条件:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

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

在使用限制条件时,如果特定集合已加载,则不会刷新;为了确保新条件生效,请应用 Populate Existing 执行选项:

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))

如果上述查询加载的特定 Parent 对象上的 children 集合已过期(例如当 Session 对象的事务提交或回滚时,或者使用了 Session.expire_all()),当下次访问 Parent.children 集合以重新加载时,Child.subelements 集合将再次使用子查询急加载。即使上述 Parent 对象是从指定了不同选项集的后续查询中访问的,情况仍然如此。要在不清除并重新加载现有对象的情况下更改选项,必须使用 Populate Existing 执行选项显式设置它们:

# 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 版本可能会增加更多操作已加载对象的加载器选项的替代方法。### 向加载器选项添加条件

用于指示加载器选项的关系属性包括在创建的联接的 ON 子句或涉及的 WHERE 条件中添加附加过滤条件的能力,具体取决于加载器策略。这可以通过使用 PropComparator.and_() 方法来实现,该方法将通过选项传递,从而将加载的结果限制为给定的过滤条件:

from sqlalchemy import select
from sqlalchemy.orm import lazyload

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

在使用限制条件时,如果特定集合已经加载,它将不会被刷新;为了确保新的条件生效,应用 Populate Existing 执行选项:

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))

如果上述查询加载的特定Parent对象上的children集合过期(例如,当Session对象的事务被提交或回滚,或使用Session.expire_all()时),当下次访问Parent.children集合以重新加载时,Child.subelements集合将再次使用子查询的急加载。即使从指定了不同选项集的后续查询中访问了上述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
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]
[<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 加一问题,即对于加载的任何 N 个对象,访问它们的延迟加载属性意味着将会发出 N+1 个 SELECT 语句。在 SQLAlchemy 中,解决 N 加一问题的常规方法是利用其非常强大的急切加载系统。然而,急切加载要求提前使用 Select 指定要加载的属性。对于可能访问未急切加载的其他属性的代码,不希望进行延迟加载,可以使用 raiseload() 策略来解决;此加载器策略将延迟加载的行为替换为引发信息性错误:

from sqlalchemy import select
from sqlalchemy.orm import raiseload

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

上面,从上述查询加载的 User 对象不会加载 .addresses 集合;如果稍后的某些代码尝试访问此属性,则会引发 ORM 异常。

raiseload() 可以与所谓的“通配符”说明符一起使用,以指示所有关系应使用此策略。例如,只设置一个属性为急切加载,而所有其他属性都为提升:

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

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

上述通配符将适用于所有关系,不仅限于除 items 外的 Order,还包括 Item 对象上的所有关系。要为仅 Order 对象设置 raiseload(),请使用 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”策略不适用于工作单元刷新过程中。这意味着如果 Session.flush() 过程需要加载集合以完成其工作,它将在绕过任何 raiseload() 指令的情况下执行此操作。

另请参阅

通配符加载策略

使用 raiseload 防止延迟列加载 ### 使用 raiseload 防止不需要的延迟加载

lazyload() 策略产生了一个最常见的对象关系映射中提到的问题之一的效果;N 加一问题,它说明对于加载的任何 N 个对象,访问它们的延迟加载属性意味着会有 N+1 个 SELECT 语句被发送。在 SQLAlchemy 中,对 N+1 问题的常规缓解方法是利用其非常强大的急切加载系统。然而,急切加载要求在前面指定要加载的属性。对于不希望进行延迟加载的其他属性的代码问题,可以使用 raiseload() 策略来解决;此加载器策略用具有信息性错误引发替换了延迟加载的行为:

from sqlalchemy import select
from sqlalchemy.orm import raiseload

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

以上,从上述查询中加载的User对象不会加载.addresses集合;如果稍后的一些代码尝试访问此属性,则会引发 ORM 异常。

可以使用 raiseload() 与所谓的“通配符”指示符一起使用,以指示所有关系都应使用此策略。例如,要设置仅一个属性作为急切加载,而其他所有属性都作为 raise:

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

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

上述通配符将适用于所有关系,而不仅限于 Orderitems 之外,还包括 Item 对象上的所有关系。要仅为 Order 对象设置 raiseload(),请指定带有 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 对象设置 raise:

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

raiseload()选项仅适用于关系属性。对于面向列的属性,defer()选项支持defer.raiseload选项,其工作方式相同。

提示

“raiseload”策略不适用于 unit of work 提交过程中。这意味着如果Session.flush()过程需要加载一个集合以完成其工作,它将通过任何raiseload()指令绕过这样做。

另请参阅

通配符加载策略

使用 raiseload 防止延迟列加载

连接式急加载

连接式急加载是 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()
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()的使用明确,这样就不会产生返回的对象在主键上的唯一性。

默认情况下发出的 JOIN 是一个 LEFT OUTER JOIN,以允许引用一个不存在相关行的主对象。对于保证具有元素的属性,例如对一个相关对象的多对一引用,其中引用的外键不为 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))

当在包含 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()
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() 创建了从 usersaddresses 的 LEFT OUTER JOIN,然而对 Address.email_address 添加的 ORDER BY 是无效的 - 查询中没有命名 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()
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()
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()
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 加载 User.addresses 集合。在这种情况下,这两个连接很可能是多余的 - 而事实上它们确实是。如果我们只想使用一个 JOIN 来加载集合并排序,我们可以使用 contains_eager() 选项,下面描述了 将明确的 JOIN/语句路由到急切加载的集合。但要了解为什么 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()
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 扮演着非常不同的角色。其中一个将精确匹配一行,即 UserAddress 的连接,其中 Address.email_address=='someaddress@foo.com'。另一个 LEFT OUTER JOIN 将匹配与 User 相关的所有 Address 行,并且仅用于填充返回的那些 User 对象的 User.addresses 集合。

通过将 joinedload() 的使用方式更改为另一种加载方式,我们可以完全独立于用于检索实际所需的 User 行的 SQL,改变集合的加载方式。以下我们将 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()
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() ### 连接式贪婪加载的禅意

由于连接式贪婪加载似乎与 Select.join() 的使用方式有很多相似之处,因此在何时以及如何使用它经常会产生困惑。重要的是要理解,虽然 Select.join() 用于更改查询的结果,但 joinedload() 却极力避免更改查询的结果,而是隐藏渲染连接的效果,以允许相关对象存在。

装载策略背后的哲学是,任何一组装载方案都可以应用于特定的查询,并且结果不会改变——只有完全加载相关对象和集合所需的 SQL 语句数量会改变。一个特定的查询可能首先使用所有的延迟加载。在上下文中使用后,可能会发现特定属性或集合总是被访问,并且更改这些的加载策略会更有效。该策略可以在不修改查询的其他部分的情况下更改,结果将保持相同,但会发出更少的 SQL 语句。理论上(实际上基本如此),无论你对 Select 做什么修改,都不会使其根据加载策略的变化加载不同的主要或相关对象集合。

如何使用joinedload()来实现不影响返回的实体行的结果,它的特点是创建查询中添加的连接的匿名别名,以便其他查询的部分不能引用它们。例如,下面的查询使用joinedload()创建了一个从usersaddresses的左外连接,但是针对Address.email_address添加的ORDER BY是无效的 - 查询中未命名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()
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()
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()
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集合。在这种情况下,这两个连接很可能是多余的 - 它们确实是。如果我们只想使用一个 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()
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 有非常不同的角色。一个将精确匹配一个行,即UserAddress的连接,其中Address.email_address=='someaddress@foo.com'。另一个左外连接将匹配与User相关的所有Address行,并且仅用于为返回的User对象填充User.addresses集合。

通过改变joinedload()的使用方式为另一种加载样式,我们可以完全独立于用于检索实际所需User行的 SQL,改变集合的加载方式。以下我们将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()
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 的元组时,这种情况目前包括 SQL Server。

使用"selectin"参数或使用selectinload()加载器选项提供了“选择 IN”急切加载。这种加载样式发出一个 SELECT,该 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()
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对象的主键值;在一批对象完全加载后,它们的主键值被注入到第二个 SELECT 的IN子句中。因为UserAddress之间的关系具有简单的主键连接条件,并且提供了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()
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 条件表达了“一”侧的主键和“多”侧的直接外键之间的相等比较,没有任何其他条件。

选择 IN 加载还支持多对多的关系,在目前的情况下,它会跨越所有三个表进行 JOIN,以匹配一边到另一边的行。

关于这种加载方式需要知道的事情包括:

  • 此策略每次会发出一个 SELECT,最多为 500 个父主键值,因为主键被渲染为 SQL 语句中的大型 IN 表达式。一些数据库,如 Oracle,对 IN 表达式的大小有硬限制,总体上,SQL 字符串的大小不应该是任意大的。

  • 由于“选择加载”依赖于 IN,对于具有复合主键的映射,它必须使用 IN 的“元组”形式,看起来像 WHERE (table.column_a, table.column_b) IN ((?, ?), (?, ?), (?, ?))。这种语法目前不受 SQL Server 支持,对于 SQLite,需要至少 3.15 版本。SQLAlchemy 中没有特殊的逻辑来提前检查哪些平台支持此语法;如果运行在不支持的平台上,数据库将立即返回错误。SQLAlchemy 之所以仅运行 SQL 以使其失败的优点是,如果特定的数据库确实开始支持此语法,则无需对 SQLAlchemy 进行任何更改(就像 SQLite 的情况一样)。

子查询预加载

旧特性

subqueryload() 预加载器在大多数情况下已经过时,被设计更简单、更灵活,例如 Yield Per 等功能的 selectinload() 策略取代,并在大多数情况下发出更有效的 SQL 语句。由于 subqueryload() 依赖于重新解释原始的 SELECT 语句,当给出非常复杂的源查询时,它可能无法有效地工作。

对于具有复合主键的对象的预加载集合的特定情况,subqueryload() 在 Microsoft SQL Server 后端上继续没有支持“元组 IN”语法的情况下仍可能有用。

子查询加载在操作上类似于选择加载,但是发出的 SELECT 语句是从原始语句派生的,并且查询结构比选择加载更复杂。

使用relationship.lazy中的"subquery"参数提供子查询即时加载,或者使用subqueryload()加载器选项。

子查询即时加载的操作是为要加载的每个关系发出第二个 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()
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 语句,与“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 提供的“批量”加载不兼容,无论是集合还是标量关系。

由于上述原因,“选择”策略应优先于“子查询”。

另请参见

选择 IN 加载

使用什么类型的加载?

使用哪种类型的加载通常归结为优化 SQL 执行次数、生成的 SQL 复杂度和获取的数据量之间的权衡。

一对多/多对多集合 - 通常最好使用selectinload()加载策略。它发出一个额外的 SELECT,尽可能少地使用表,不影响原始语句,并且对于任何类型的起始查询都是最灵活的。它唯一的主要限制是在使用不支持“tuple IN”的后端上使用具有复合主键的表,目前包括 SQL Server 和非常旧的 SQLite 版本;所有其他包含的后端都支持它。

多对一 - joinedload()策略是最通用的策略。在特殊情况下,如果存在非常少量的潜在相关值,则immediateload()策略也可能有用,因为如果相关对象已经存在,则此策略将从本地Session获取对象而不发出任何 SQL。

多态急加载

支持在每个急加载基础上指定多态选项。请参见 Eager Loading of Polymorphic Subtypes 部分,了解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('*')选项将取代所有正在使用的relationship()构造的lazy设置,但不包括那些使用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()选项之前还是之后,如果传递了包含 "*" 的多个选项,则最后一个选项将生效。

每个实体的通配符加载策略

通配符加载策略的变体是能够按实体基础设置策略的能力。例如,如果查询UserAddress,我们可以指示Address上的所有关系使用延迟加载,同时通过首先应用Load对象,然后将 * 指定为链接选项:

from sqlalchemy import select
from sqlalchemy.orm import Load

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

上述,Address上的所有关系都将设置为延迟加载。### 每个实体的通配符加载策略

通配符加载策略的变体是能够按实体基础设置策略的能力。例如,如果查询UserAddress,我们可以指示Address上的所有关系使用延迟加载,同时通过首先应用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() 等方法。下面,我们指定了 UserAddress 之间的连接,并额外将其作为 User.addresses 的急切加载的基础:

from sqlalchemy.orm import contains_eager

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

如果语句的“急切”部分是“别名”,则应使用 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()
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 

作为参数给出的路径必须是从起始实体开始的完整路径,例如,如果我们正在加载 Users->orders->Order->items->Item,则选项将如下使用:

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

使用 contains_eager() 来加载自定义过滤的集合结果。

当我们使用 contains_eager() 时,我们是自己构造将用于填充集合的 SQL。由此自然而然地,我们可以选择 修改 集合意图存储的值,通过编写我们的 SQL 来加载集合或标量属性的元素子集。

提示

SQLAlchemy 现在有了一个 更简单的方法 来实现这一点,即允许将 WHERE 条件直接添加到加载器选项,如 joinedload()selectinload() 使用 PropComparator.and_()。参见 Adding Criteria to loader options 章节中的示例。

如果相关的集合要使用比简单的 WHERE 子句更复杂的 SQL 条件或修饰符进行查询,这里描述的技术仍然适用。

举例来说,我们可以加载一个User对象,并仅急切地加载其中特定的地址到其.addresses集合中,方法是通过过滤连接的数据,并使用 contains_eager() 路由它,同时还使用 Populate Existing 确保任何已加载的集合都被覆盖:

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

上面的查询将仅加载包含至少一个包含子字符串'aol.com'email字段的Address对象的User对象;User.addresses集合将包含这些Address条目,而不包括实际与该集合关联的任何其他Address条目。

提示

在所有情况下,除非有明确的指示要这样做,否则 SQLAlchemy ORM 不会覆盖已加载的属性和集合。由于正在使用标识映射,通常情况下,ORM 查询返回的对象实际上已经存在并加载到内存中。因此,当使用contains_eager()以另一种方式填充集合时,通常最好像上面所示那样使用填充现有,以便已加载的集合使用新数据进行刷新。populate_existing 选项将重置已存在的所有属性,包括挂起的更改,因此在使用之前请确保刷新所有数据。使用具有其默认行为的Session的 autoflush 足以。

注意

使用contains_eager()加载的定制集合不是“粘性”的;也就是说,下次加载该集合时,它将以其通常的默认内容加载。如果对象过期,则集合可能会重新加载,这在使用默认会话设置时每当使用 Session.commit()Session.rollback() 方法时,或者使用 Session.expire_all()Session.expire() 方法时会发生。

另请参阅

向加载器选项添加条件 - 现代 API 允许在任何关系加载器选项中直接添加 WHERE 条件

使用 contains_eager() 加载定制过滤的集合结果

当我们使用contains_eager()时,我们正在构建用于填充集合的 SQL。由此自然而然地,我们可以选择修改集合的预期存储值,通过编写我们的 SQL 以加载集合或标量属性的子集元素。

提示

SQLAlchemy 现在有一种更简单的方法来做到这一点,即允许将 WHERE 条件直接添加到加载器选项,例如joinedload()selectinload(),使用PropComparator.and_()。有关示例,请参见向加载器选项添加条件部分。

如果要使用比简单的 WHERE 子句更复杂的 SQL 条件或修改器查询相关集合,则这里描述的技术仍然适用。

例如,我们可以加载一个User对象,并且仅通过过滤联接数据并使用contains_eager()将其路由到.addresses集合,从而急切地加载特定地址,还使用 Populate Existing 确保任何已加载的集合都被覆盖:

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

上面的查询将仅加载包含其email字段中包含子字符串'aol.com'Address对象的User对象;User.addresses集合将包含这些Address条目,而不是与集合实际相关联的任何其他Address条目。

提示

在所有情况下,SQLAlchemy ORM 不会覆盖已加载的属性和集合,除非被告知要这样做。由于正在使用身份映射,通常情况下,ORM 查询返回的对象实际上已经存在并且在内存中已加载。因此,当使用contains_eager()以替代方式填充集合时,通常最好使用上面示例中所示的 Populate Existing,以便已加载的集合可以用新数据刷新。populate_existing选项将重置已经存在的所有属性,包括待处理的更改,因此请确保在使用之前刷新所有数据。使用具有其默认行为的Session的 autoflush 足以。

注意

我们使用contains_eager()加载的定制集合不是“粘性”的;也就是说,下次加载此集合时,它将以其通常的默认内容加载。 如果对象过期,则可能重新加载集合,这会在使用默认会话设置时每当使用Session.commit()Session.rollback() 方法,或者使用Session.expire_all()Session.expire() 方法时发生。

另请参阅

向加载器选项添加条件 - 现代 API 允许在任何关系加载器选项中直接添加 WHERE 条件

关系加载器 API

对象名称 描述
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 急切加载来加载给定属性。
subqueryload(*keys) 表示应使用子查询急切加载来加载给定属性。
function sqlalchemy.orm.contains_eager(*keys: Literal['*'] | 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: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

指示属性应使用其预定义的加载器样式加载。

此加载选项的行为是不更改属性的当前加载样式,这意味着将使用先前配置的样式,或者如果没有选择先前的样式,则将使用默认加载。

此方法用于将其他加载器选项链接到属性链中的进一步位置,而不更改链中的链接的加载器样式。例如,要为元素的元素设置连接的急切加载:

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

defaultload() 也可用于在相关类上设置列级选项,即 defer()undefer()

session.scalars(
    select(MyClass).options(
        defaultload(MyClass.someattribute)
        .defer("some_column")
        .undefer("some_other_column")
    )
)

另请参见

使用 Load.options() 指定子选项

Load.options()

function sqlalchemy.orm.immediateload(*keys: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → _AbstractLoad

指示应使用立即加载和每个属性的 SELECT 语句加载给定属性。

加载是使用“懒加载器”策略实现的,不会触发任何额外的急切加载器。

immediateload() 选项通常被 selectinload() 选项替代,后者通过为所有加载的对象发出 SELECT 语句来更有效地执行相同的任务。

此函数是 Load 接口的一部分,并支持方法链接和独立操作。

参数:

recursion_depth – 递归深度

可选整数;当与自引用关系一起设置为正整数时,表示“selectin”加载将自动继续这么多级别,直到找不到任何项目为止。

注意

immediateload.recursion_depth选项目前仅支持自引用关系。目前还没有选项可以自动遍历涉及多个关系的递归结构。

警告

此参数是新的实验性参数,应该视为“alpha”状态

2.0 版本中新增:添加immediateload.recursion_depth

另请参见

关系加载技术

Select IN 加载

function sqlalchemy.orm.joinedload(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) → _AbstractLoad

表示应该使用连接的快速加载来加载给定的属性。

此函数是Load接口的一部分,支持方法链和独立操作。

示例:

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

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

# lazily load Order.items, but when Items are loaded,
# joined-load the keywords collection
select(Order).options(
    lazyload(Order.items).joinedload(Item.keywords)
)

参数:

innerjoin

如果True,表示连接的急切加载应该使用内部连接而不是左外连接的默认值:

select(Order).options(joinedload(Order.user, innerjoin=True))

为了链接多个急切加载在一起,其中一些可能是 OUTER,另一些是 INNER,右嵌套连接用于链接它们:

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

上述查询通过“outer” join 连接 A.bs 和通过“inner” join 连接 B.cs,将连接呈现为“a LEFT OUTER JOIN (b JOIN c)”。

innerjoin标志也可以用术语"unnested"来表示。这表示应该使用 INNER JOIN,除非连接到左侧的 LEFT OUTER JOIN,这种情况下它将呈现为 LEFT OUTER JOIN。例如,假设A.bs是一个 outerjoin:

select(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”标志不会影响从多对多关联表(例如配置为relationship.secondary的表)到目标表的 JOIN 渲染;为了结果的正确性,这些连接始终是 INNER 的,因此如果连接到 OUTER join,它们将是右嵌套的。

注意

joinedload()生成的连接是匿名别名。连接进行的条件无法修改,ORM 启用的Select或传统的Query也不能以任何方式引用这些连接,包括排序。有关详细信息,请参见急切加载之道。

要生成一个明确可用的特定 SQL JOIN,请使用Select.join()Query.join()。要将显式的 JOIN 与集合的急加载结合起来,请使用contains_eager();参见将显式 JOIN/语句路由到急加载的集合。

另请参阅

关系加载技术

连接急加载

function sqlalchemy.orm.lazyload(*keys: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

表示应该使用“懒加载”来加载给定的属性。

此函数是Load接口的一部分,支持方法链和独立操作。

另请参阅

关系加载技术

懒加载

class sqlalchemy.orm.Load

表示加载器选项,用于修改 ORM 启用的Select或传统Query的状态,以影响加载各种映射属性的方式。

在大多数情况下,当使用查询选项像joinedload()defer()或类似的选项时,Load对象通常会在幕后隐式使用。除了一些非常特殊的情况外,通常不会直接实例化它。

另请参阅

每个实体的通配符加载策略 - 演示了直接使用Load可能有用的示例

成员

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.Loadsqlalchemy.orm.strategy_options._AbstractLoad

method contains_eager(attr: _AttrType, alias: _FromClauseArgument | None = None, _is_chain: bool = False) → Self

sqlalchemy.orm.strategy_options._AbstractLoad.contains_eager 方法继承 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的带有contains_eager()选项的Load对象。

查看contains_eager()的使用示例。

method defaultload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

sqlalchemy.orm.strategy_options._AbstractLoad.defaultload 方法继承 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的带有defaultload()选项的Load对象。

查看defaultload()的使用示例。

method defer(key: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → Self

sqlalchemy.orm.strategy_options._AbstractLoad.defer 方法继承 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的带有defer()选项的Load对象。

查看defer()的使用示例。

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

HasTraverseInternals.get_children() 方法继承 HasTraverseInternals

返回此HasTraverseInternals的即时子项HasTraverseInternals

用于访问遍历。

**kw 可能包含更改返回集合的标志,例如返回子项的子集以减少较大的遍历,或者从不同上下文返回子项(例如模式级集合而不是从子句级返回的)。

method immediateload(attr: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → Self

sqlalchemy.orm.strategy_options._AbstractLoad.immediateload 方法继承 sqlalchemy.orm.strategy_options._AbstractLoad

生成一个新的带有immediateload()选项的Load对象。

查看immediateload()的使用示例。

attribute inherit_cache: bool | None = None

HasCacheKey HasCacheKey.inherit_cache 属性继承

指示此HasCacheKey实例是否应使用其直接超类使用的缓存键生成方案。

该属性默认为None,表示构造尚未考虑是否适合参与缓存;这在功能上等效于将值设置为False,但还会发出警告。

如果对象对应的 SQL 不基于本类的属性而是本类的父类属性,则可以将此标志设置为True

另请参阅

为自定义构造启用缓存支持 - 有关为第三方或用户定义的 SQL 构造设置HasCacheKey.inherit_cache属性的一般指南。

method joinedload(attr: Literal['*'] | QueryableAttribute[Any], innerjoin: bool | None = None) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.joinedload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用joinedload()选项生成一个新的Load对象。

请参阅joinedload()以查看用法示例。

method lazyload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.lazyload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用lazyload()选项生成一个新的Load对象。

请参阅lazyload()以查看用法示例。

method load_only(*attrs: Literal['*'] | QueryableAttribute[Any], raiseload: bool = False) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.load_only 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用load_only()选项生成一个新的Load对象。

请参阅load_only()以查看用法示例。

method noload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.noload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用noload()选项生成一个新的Load对象。

请参阅noload()以查看用法示例。

method 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 中新增。

另请参阅

defaultload()

使用 Load.options() 指定子选项

method process_compile_state(compile_state: ORMCompileState) → None

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.process_compile_state 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

对给定的ORMCompileState应用修改。

此方法是特定CompileStateOption的实现的一部分,仅在编译 ORM 查询时内部调用。

method 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 propagate_to_loaders: bool

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.propagate_to_loaders 属性的 sqlalchemy.orm.strategy_options._AbstractLoad

如果为 True,则表示此选项应该传递到关系懒加载器的“次要”SELECT 语句,以及属性加载/刷新操作。

method raiseload(attr: Literal['*'] | QueryableAttribute[Any], sql_only: bool = False) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.raiseload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用raiseload()选项生成一个新的Load对象。

查看raiseload()以查看用法示例。

method selectin_polymorphic(classes: Iterable[Type[Any]]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.selectin_polymorphic 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用selectin_polymorphic()选项生成一个新的Load对象。

查看selectin_polymorphic()以查看用法示例。

method selectinload(attr: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.selectinload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用selectinload()选项生成一个新的Load对象。

查看selectinload()以查看用法示例。

method subqueryload(attr: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.subqueryload 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用subqueryload()选项生成一个新的Load对象。

查看subqueryload()以查看用法示例。

method undefer(key: Literal['*'] | QueryableAttribute[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.undefer 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用undefer()选项生成一个新的Load对象。

请参见undefer()获取用法示例。

method undefer_group(name: str) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.undefer_group 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用undefer_group()选项生成新的Load对象。

请参见undefer_group()获取用法示例。

method with_expression(key: _AttrType, expression: _ColumnExpressionArgument[Any]) → Self

继承自 sqlalchemy.orm.strategy_options._AbstractLoad.with_expression 方法的 sqlalchemy.orm.strategy_options._AbstractLoad

使用with_expression()选项生成新的Load对象。

请参见with_expression()获取用法示例。

function sqlalchemy.orm.noload(*keys: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

表示给定的关系属性应保持未加载状态。

当访问关系属性时,关系属性将返回None,而不产生任何加载效果。

此功能是Load接口的一部分,支持方法链和独立操作。

noload()仅适用于relationship()属性。

注意

使用relationship.lazy参数将此加载策略设置为关系的默认策略可能会导致刷新时出现问题,例如,如果删除操作需要加载相关对象,而返回的是None

参见

关系加载技术

function sqlalchemy.orm.raiseload(*keys: Literal['*'] | QueryableAttribute[Any], **kw: Any) → _AbstractLoad

表示如果访问给定的属性,应该引发错误。

使用raiseload()配置的关系属性在访问时将引发InvalidRequestError。这种方式通常很有用,当应用程序试图确保在特定上下文中访问的所有关系属性都已通过急加载加载时。这种策略将导致立即引发异常,而不必查看 SQL 日志以确保不会发生延迟加载。

raiseload() 仅适用于 relationship() 属性。为了将在基于列的属性上应用 SQL 异常处理行为,应在 defer() 加载器选项的 defer.raiseload 参数上使用。

参数:

sql_only – 如果为 True,则仅在延迟加载会发出 SQL 时引发异常,但如果仅检查标识映射或确定相关值由于缺少键应为 None,则不会引发异常。当为 False 时,该策略将引发所有类型的关系加载异常。

此函数是 Load 接口的一部分,支持方法链式和独立操作。

另请参阅

关系加载技术

使用 raiseload 防止不需要的延迟加载

使用 raiseload 防止延迟加载列

function sqlalchemy.orm.selectinload(*keys: Literal['*'] | QueryableAttribute[Any], recursion_depth: int | None = None) → _AbstractLoad

指示应使用 SELECT IN 即时加载来加载给定的属性。

此函数是 Load 接口的一部分,支持方法链式和独立操作。

示例:

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

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

# lazily load Order.items, but when Items are loaded,
# selectin-load the keywords collection
select(Order).options(
    lazyload(Order.items).selectinload(Item.keywords)
)

参数:

递归深度

可选整数;当与自引用关系一起设置为正整数时,表示“选择加载”将自动深入到指定的层级直到找不到任何项目。

注意

selectinload.recursion_depth 选项目前仅支持自引用关系。目前还没有自动遍历多个关系的递归结构的选项。

此外,selectinload.recursion_depth 参数是新的实验性参数,并且应被视为 2.0 系列的“alpha”状态。

2.0 版本中新增:添加 selectinload.recursion_depth

另请参阅

关系加载技术

选择 IN 加载

function sqlalchemy.orm.subqueryload(*keys: Literal['*'] | QueryableAttribute[Any]) → _AbstractLoad

指示应使用子查询即时加载来加载给定的属性。

此函数是 Load 接口的一部分,支持方法链式和独立操作。

示例:

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

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

# lazily load Order.items, but when Items are loaded,
# subquery-load the keywords collection
select(Order).options(
    lazyload(Order.items).subqueryload(Item.keywords)
)

另请参阅

关系加载技术

子查询即时加载

用于查询的 ORM API 功能

原文:docs.sqlalchemy.org/en/20/orm/queryguide/api.html

ORM 加载选项

加载选项是一种对象,当传递给Select.options()方法的Select对象或类似的 SQL 构造时,会影响列和关系属性的加载。大多数加载选项都来自于Load层次结构。有关使用加载选项的完整概述,请参阅下面的链接部分。

另请参阅

  • 列加载选项 - 详细说明了影响列和 SQL 表达式映射属性加载方式的映射器和加载选项

  • 关系加载技术 - 详细说明了影响relationship()映射属性加载方式的关系和加载选项

ORM 执行选项

ORM 级别的执行选项是可以通过Session.execute.execution_options参数关联到语句执行的关键字选项,该参数是由Session方法(例如Session.execute()Session.scalars())接受的字典参数,或者直接使用Executable.execution_options()方法将其与要调用的语句直接关联起来,该方法接受它们作为任意关键字参数。

ORM 级别的选项与 Core 级别的执行选项有所不同,Core 级别的执行选项记录在Connection.execution_options()。重要的是要注意,下面讨论的 ORM 选项与 Core 级别的方法Connection.execution_options()Engine.execution_options()兼容;即使EngineConnection与正在使用的Session关联,这些选项也会在此级别被忽略。

在这个部分中,将展示Executable.execution_options()方法的样式示例。

刷新现有对象

populate_existing执行选项确保对于加载的所有行,Session中对应的实例将完全被刷新 - 擦除对象中的任何现有数据(包括未决的更改),并用从结果加载的数据替换。

示例用法如下:

>>> stmt = select(User).execution_options(populate_existing=True)
>>> result = session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
... 

通常,ORM 对象只加载一次,如果它们在后续结果行中与主键匹配,则不会将该行应用于对象。这既是为了保留对象上未提交的更改,也是为了避免刷新已经存在的数据的开销和复杂性。Session假设一个高度隔离的事务的默认工作模型,并且在事务中预期的数据发生变化程度超出正在进行的本地更改时,这些用例将使用显式步骤来处理,例如此方法。

使用populate_existing,可以刷新与查询匹配的任何一组对象,并且还可以控制关系加载器选项。例如,要同时刷新一个实例和其相关对象集:

stmt = (
    select(User)
    .where(User.name.in_(names))
    .execution_options(populate_existing=True)
    .options(selectinload(User.addresses))
)
# will refresh all matching User objects as well as the related
# Address objects
users = session.execute(stmt).scalars().all()

populate_existing的另一个用例是支持各种属性加载功能,这些功能可以根据每个查询的情况改变如何加载属性。适用于此选项的选项包括:

  • with_expression()选项

  • PropComparator.and_() 方法可以修改加载器策略加载的内容

  • contains_eager() 选项

  • with_loader_criteria() 选项

  • load_only() 选项来选择要刷新的属性

populate_existing 执行选项相当于 1.x 风格 ORM 查询中的 Query.populate_existing() 方法。

请参见

我使用 Session 重新加载数据,但它没有看到我在其他地方提交的更改 - 在 常见问题解答

刷新 / 过期 - 在 ORM Session 文档中 ### 自动刷新

当作为 False 传递时,此选项将导致 Session 不调用 “自动刷新” 步骤。这相当于使用 Session.no_autoflush 上下文管理器禁用自动刷新:

>>> stmt = select(User).execution_options(autoflush=False)
>>> session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
... 

此选项也适用于启用 ORM 的 UpdateDelete 查询。

autoflush 执行选项相当于 1.x 风格 ORM 查询中的 Query.autoflush() 方法。

请参见

刷新 ### 使用 Yield Per 获取大型结果集

yield_per 执行选项是一个整数值,它将导致 Result 一次只缓冲有限数量的行和/或 ORM 对象,然后才将数据提供给客户端。

通常,ORM 会立即获取所有行,为每个行构造 ORM 对象,并将这些对象组装到单个缓冲区中,然后将此缓冲区作为要返回的行的来源传递给Result对象。此行为的基本原理是允许对诸如联接急加载、结果唯一化以及依赖于标识映射为每个对象在结果集中被提取时保持一致状态的结果处理逻辑等功能的正确行为。

yield_per选项的目的是更改此行为,以使 ORM 结果集针对迭代非常大的结果集(例如> 10K 行)进行了优化,其中用户已确定上述模式不适用。当使用yield_per时,ORM 将将 ORM 结果批处理到子集合中,并在Result对象被迭代时逐个从每个子集合中产生行,以便 Python 解释器无需声明非常大的内存区域,这既费时又导致内存使用过多。该选项影响数据库游标的使用方式以及 ORM 构造行和对象以传递给Result的方式。

提示

由上可知,Result必须以可迭代的方式被消耗,即使用迭代,如for row in result或使用部分行方法,如Result.fetchmany()Result.partitions()。调用Result.all()将失去使用yield_per的目的。

使用yield_per相当于同时使用Connection.execution_options.stream_results执行选项,该选项选择在支持的情况下由后端使用服务器端游标,并且返回的Result对象上的Result.yield_per()方法,该方法建立了要提取的行的固定大小以及一次构造的 ORM 对象的相应限制。

提示

现在yield_per也作为 Core 执行选项可用,详细描述在使用服务器端游标(又名流式结果)。本节详细介绍了将yield_per作为 ORM Session的执行选项的用法。该选项在两种情况下的行为尽可能相似。

当与 ORM 一起使用时,yield_per必须通过给定语句上的Executable.execution_options()方法或通过将其传递给Session.execute()Session.execute.execution_options参数来建立,或者通过其他类似的Session方法,例如Session.scalars()。下面是获取 ORM 对象的典型用法:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for user_obj in session.scalars(stmt):
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

上述代码等同于下面的示例,该示例使用了Connection.execution_options.stream_resultsConnection.execution_options.max_row_buffer Core 级别执行选项,以及Result.yield_per()方法的使用Result

# equivalent code
>>> stmt = select(User).execution_options(stream_results=True, max_row_buffer=10)
>>> for user_obj in session.scalars(stmt).yield_per(10):
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

yield_per也常与Result.partitions()方法结合使用,该方法将对分组分区中的行进行迭代。每个分区的大小默认为传递给yield_per的整数值,如下例所示:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for partition in session.scalars(stmt).partitions():
...     for user_obj in partition:
...         print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

当使用集合时,yield_per执行选项“子查询”急加载加载或“连接”急加载不兼容。如果数据库驱动程序支持多个独立游标,则它可能与“选择内”急加载兼容。

此外,yield_per执行选项与Result.unique()方法不兼容;由于此方法依赖于为所有行存储完整的标识集,因此它必然会破坏使用yield_per的目的,即处理任意数量的行。

自 1.4.6 版本更改:当从Result对象中获取 ORM 行时,该对象使用Result.unique()过滤器,并同时使用yield_per执行选项时,会引发异常。

当在 1.x 风格 ORM 使用遗留Query对象时,Query.yield_per()方法的结果与yield_per执行选项相同。

另请参阅

使用服务器端游标(又称流式结果) ### 身份令牌

深层魔法

此选项是一个高级功能,主要用于与水平分片扩展一起使用。对于从不同“分片”或分区加载具有相同主键的对象的典型情况,请首先考虑每个分片使用单独的Session对象。

“身份令牌”是可以与新加载对象的标识键相关联的任意值。此元素首先存在于支持按行“分片”的扩展中,其中对象可以从特定数据库表的任意数量的副本加载,尽管这些副本具有重叠的主键值。 “身份令牌”的主要消费者是水平分片扩展,它提供了一个在特定数据库表的多个“分片”之间持久化对象的通用框架。

identity_token执行选项可以在每个查询的基础上直接影响此令牌。直接使用它,可以为Session填充具有相同主键和源表但具有不同“标识”的对象的多个实例。

其中一个示例是使用翻译模式名称功能,该功能可以影响查询范围内的模式选择,从具有相同名称的表中填充Session对象。给定一个映射如下:

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

class Base(DeclarativeBase):
    pass

class MyTable(Base):
    __tablename__ = "my_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

上述类的默认“模式”名称是 None,这意味着 SQL 语句中不会写入模式限定符。但是,如果我们使用 Connection.execution_options.schema_translate_map,将 None 映射到替代模式,我们可以将 MyTable 的实例放入两个不同的模式中:

engine = create_engine(
    "postgresql+psycopg://scott:tiger@localhost/test",
)

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema"})
) as sess:
    sess.add(MyTable(name="this is schema one"))
    sess.commit()

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema_2"})
) as sess:
    sess.add(MyTable(name="this is schema two"))
    sess.commit()

上述两个块分别创建了一个与不同模式映射关联的 Session 对象,并且 MyTable 的一个实例被持久化到了 test_schema.my_tabletest_schema_2.my_table 中。

上述 Session 对象是独立的。如果我们想要在一个事务中持久化这两个对象,我们需要使用 水平分片 扩展来实现这一点。

但是,我们可以通过一个会话来说明对这些对象的查询,如下所示:

with Session(engine) as sess:
    obj1 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema"},
            identity_token="test_schema",
        )
    )
    obj2 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema_2"},
            identity_token="test_schema_2",
        )
    )

obj1obj2 都彼此不同。但是,它们都引用了 MyTable 类的主键 id 1,但是是不同的。这就是 identity_token 发挥作用的方式,我们可以在检查每个对象时看到,其中我们查看 InstanceState.key 来查看两个不同的标识令牌:

>>> from sqlalchemy import inspect
>>> inspect(obj1).key
(<class '__main__.MyTable'>, (1,), 'test_schema')
>>> inspect(obj2).key
(<class '__main__.MyTable'>, (1,), 'test_schema_2')

当使用 水平分片 扩展时,上述逻辑会自动发生。

版本 2.0.0rc1 中的新功能:- 添加了 identity_token ORM 级别的执行选项。

另请参阅

水平分片 - 在 ORM 示例 部分。请参阅脚本 separate_schema_translates.py,了解如何使用完整的分片 API 进行上述用例的演示。

检查来自启用 ORM 的 SELECT 和 DML 语句的实体和列

select() 构造,以及 insert()update()delete() 构造(对于后者的 DML 构造,在 SQLAlchemy 1.4.33 中),都支持检查这些语句所针对的实体,以及将在结果集中返回的列和数据类型。

对于 Select 对象,此信息可从 Select.column_descriptions 属性获取。此属性的操作方式与传统的 Query.column_descriptions 属性相同。返回的格式是一个字典列表:

>>> from pprint import pprint
>>> user_alias = aliased(User, name="user2")
>>> stmt = select(User, User.id, user_alias)
>>> pprint(stmt.column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'type': <class 'User'>},
 {'aliased': False,
 'entity': <class 'User'>,
 'expr': <....InstrumentedAttribute object at ...>,
 'name': 'id',
 'type': Integer()},
 {'aliased': True,
 'entity': <AliasedClass ...; User>,
 'expr': <AliasedClass ...; User>,
 'name': 'user2',
 'type': <class 'User'>}]

当与非 ORM 对象一起使用 Select.column_descriptions 时,诸如普通 TableColumn 对象,条目将在所有情况下包含有关返回的各个列的基本信息:

>>> stmt = select(user_table, address_table.c.id)
>>> pprint(stmt.column_descriptions)
[{'expr': Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
 'name': 'id',
 'type': Integer()},
 {'expr': Column('name', String(), table=<user_account>, nullable=False),
 'name': 'name',
 'type': String()},
 {'expr': Column('fullname', String(), table=<user_account>),
 'name': 'fullname',
 'type': String()},
 {'expr': Column('id', Integer(), table=<address>, primary_key=True, nullable=False),
 'name': 'id_1',
 'type': Integer()}]

在版本 1.4.33 中进行了更改:当对非 ORM 启用的 Select 使用 Select.column_descriptions 属性时,现在会返回一个值。以前会引发 NotImplementedError

对于 insert()update()delete() 构造,有两个单独的属性。一个是 UpdateBase.entity_description,它返回有关 DML 构造将影响的主要 ORM 实体和数据库表的信息:

>>> from sqlalchemy import update
>>> stmt = update(User).values(name="somename").returning(User.id)
>>> pprint(stmt.entity_description)
{'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'table': Table('user_account', ...),
 'type': <class 'User'>}

提示

UpdateBase.entity_description 包括一个条目 "table",实际上是语句中将要插入、更新或删除的,这并不总是与该类可能被映射到的 SQL “selectable” 相同。例如,在连接表继承方案中,"table" 将引用给定实体的本地表。

另一个是UpdateBase.returning_column_descriptions,它以与Select.column_descriptions大致相似的方式提供有关 RETURNING 集合中列的信息:

>>> pprint(stmt.returning_column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute ...>,
 'name': 'id',
 'type': Integer()}]

版本 1.4.33 中的新功能:增加了UpdateBase.entity_descriptionUpdateBase.returning_column_descriptions属性。 #### 其他 ORM API 结构

对象名称 描述
aliased(element[, alias, name, flat, ...]) 生成给定元素的别名,通常是一个AliasedClass实例。
AliasedClass 代表用于 Query 的映射类的“别名”形式。
AliasedInsp AliasedClass对象提供检查接口。
Bundle 一组在一个命名空间下由Query返回的 SQL 表达式。
join(left, right[, onclause, isouter, ...]) 生成左右子句之间的内部连接。
outerjoin(left, right[, onclause, full]) 生成左外连接 left 和 right 子句之间的左外连接。
with_loader_criteria(entity_or_base, where_criteria[, loader_only, include_aliases, ...]) 为特定实体的所有出现加载添加额外的 WHERE 条件。
with_parent(instance, prop[, from_entity]) 创建将此查询的主实体与给定相关实例相关联的过滤条件,使用已建立的relationship()配置。
function sqlalchemy.orm.aliased(element: _EntityType[_O] | FromClause, alias: FromClause | None = None, name: str | None = None, flat: bool = False, adapt_on_names: bool = False) → AliasedClass[_O] | FromClause | AliasedType[_O]

生成给定元素的别名,通常是一个AliasedClass实例。

例如:

my_alias = aliased(MyClass)

stmt = select(MyClass, my_alias).filter(MyClass.id > my_alias.id)
result = session.execute(stmt)

aliased() 函数用于将映射类的临时映射创建为新的可选项。默认情况下,从通常的映射可选项(通常是一个Table )使用FromClause.alias() 方法生成可选项。但是,aliased() 也可以用于将类链接到新的select() 语句。此外,with_polymorphic() 函数是aliased() 的一个变体,旨在指定所谓的“多态可选项”,它对应于一次连接继承多个子类的联合。

为了方便起见,aliased() 函数还接受普通的FromClause 构造,例如Tableselect() 构造。在这些情况下,对象调用FromClause.alias() 方法,并返回新的Alias 对象。在这种情况下,返回的Alias 未在 ORM 中映射。

另请参阅

ORM 实体别名 - 在 SQLAlchemy 统一教程 中

选择 ORM 别名 - 在 ORM 查询指南 中

参数:

  • element – 要别名化的元素。通常是一个映射类,但为了方便起见,也可以是一个FromClause 元素。

  • alias – 可选的可选择单元,用于将元素映射到。通常用于将对象链接到子查询,并且应该是一个别名选择构造,就像从 Query.subquery() 方法或 Select.subquery()Select.alias() 方法的 select() 构造产生的一样。

  • name – 用于别名的可选字符串名称,如果未由 alias 参数指定。名称,除其他外,形成了将通过 Query 对象返回的元组访问的属性名称。创建 Join 对象的别名时不支持。

  • flat – 布尔值,将传递给 FromClause.alias() 调用,以便 Join 对象的别名将别名加入联接内部的各个表,而不是创建子查询。这通常由所有现代数据库支持,关于右嵌套联接通常生成更有效的查询。

  • adapt_on_names

    如果为 True,则在将 ORM 实体的映射列与给定可选择的映射时将使用更宽松的 “匹配” - 如果给定的可选择没有与实体上的列对应的列,则将执行基于名称的匹配。此用例是将实体与某些派生的可选择相关联,例如使用聚合函数的可选择:

    class UnitPrice(Base):
     __tablename__ = 'unit_price'
     ...
     unit_id = Column(Integer)
     price = Column(Numeric)
    
    aggregated_unit_price = Session.query(
     func.sum(UnitPrice.price).label('price')
     ).group_by(UnitPrice.unit_id).subquery()
    
    aggregated_unit_price = aliased(UnitPrice,
     alias=aggregated_unit_price, adapt_on_names=True)
    

    在上面,对 aggregated_unit_price 上的函数引用 .price 将返回 func.sum(UnitPrice.price).label('price') 列,因为它与名称 “price” 匹配。通常情况下,“price” 函数不会与实际的 UnitPrice.price 列有任何 “列对应”,因为它不是原始的代理。

class sqlalchemy.orm.util.AliasedClass

表示用于 Query 的映射类的“别名”形式。

ORM 等效于 alias() 构造,此对象使用 __getattr__ 方案模拟映射类,并维护对真实 Alias 对象的引用。

AliasedClass的一个主要目的是在 ORM 生成的 SQL 语句中作为一个替代,以便在多个上下文中使用现有映射实体。一个简单的例子:

# find all pairs of users with the same name
user_alias = aliased(User)
session.query(User, user_alias).\
 join((user_alias, User.id > user_alias.id)).\
 filter(User.name == user_alias.name)

AliasedClass还能够将现有映射类映射到一个全新的可选择项,只要这个可选择项与现有映射可选择项兼容,并且还可以在映射中配置为relationship()的目标。查看下面的链接以获取示例。

通常使用aliased()函数构造AliasedClass对象。当使用with_polymorphic()函数时,还会产生附加配置。

结果对象是AliasedClass的一个实例。该对象实现了一个属性方案,产生与原始映射类相同的属性和方法接口,允许AliasedClass与在原始类上有效的任何属性技术兼容,包括混合属性(参见混合属性)。

可以使用inspect()检查AliasedClass的基础Mapper、别名可选择和其他信息:

from sqlalchemy import inspect
my_alias = aliased(MyClass)
insp = inspect(my_alias)

结果检查对象是AliasedInsp的一个实例。

另请参见

aliased()

with_polymorphic()

与别名类的关系

使用窗口函数限制行关系

类签名

sqlalchemy.orm.AliasedClass (sqlalchemy.inspection.Inspectable, sqlalchemy.orm.ORMColumnsClauseRole)

class sqlalchemy.orm.util.AliasedInsp

AliasedClass对象提供检查接口。

给定一个 AliasedClass,使用 inspect() 函数将返回 AliasedInsp 对象:

from sqlalchemy import inspect
from sqlalchemy.orm import aliased

my_alias = aliased(MyMappedClass)
insp = inspect(my_alias)

AliasedInsp 的属性包括:

  • entity - 所代表的 AliasedClass

  • mapper - 映射底层类的 Mapper

  • selectable - 最终表示别名的 Alias 构造或 Select 构造。

  • name - 别名的名称。 在从 Query 中返回结果元组时,也用作属性名称。

  • with_polymorphic_mappers - 表示在 AliasedClass 的 select 构造中表达的所有那些映射器的集合。

  • polymorphic_on - 一个替代列或 SQL 表达式,将用作多态加载的“辨别器”。

另请参阅

运行时检查 API

类签名

sqlalchemy.orm.AliasedInsp (sqlalchemy.orm.ORMEntityColumnsClauseRole, sqlalchemy.orm.ORMFromClauseRole, sqlalchemy.sql.cache_key.HasCacheKey, sqlalchemy.orm.base.InspectionAttr, sqlalchemy.util.langhelpers.MemoizedSlots, sqlalchemy.inspection.Inspectable, typing.Generic)

class sqlalchemy.orm.Bundle

一组由 Query 在一个命名空间下返回的 SQL 表达式。

Bundle 本质上允许列导向的 Query 对象返回的基于元组的结果嵌套。 它还可以通过简单的子类化进行扩展,其中主要的重写功能是如何返回表达式集,允许后处理以及自定义返回类型,而不涉及 ORM 标识映射的类。

另请参阅

属性捆绑

成员

init(), c, columns, create_row_processor(), is_aliased_class, is_bundle, is_clause_element, is_mapper, label(), single_entity

类签名

sqlalchemy.orm.Bundle (sqlalchemy.orm.ORMColumnsClauseRole, sqlalchemy.sql.annotation.SupportsCloneAnnotations, sqlalchemy.sql.cache_key.MemoizedHasCacheKey, sqlalchemy.inspection.Inspectable, sqlalchemy.orm.base.InspectionAttr)

method __init__(name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any)

构建一个新的Bundle

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

for row in session.query(bn).filter(
 bn.c.x == 5).filter(bn.c.y == 4):
 print(row.mybundle.x, row.mybundle.y)

参数:

  • name – bundle 的名称。

  • *exprs – 组成 bundle 的列或 SQL 表达式。

  • single_entity=False – 如果为 True,则此Bundle的行可以像映射实体一样在任何封闭元组之外作为“单个实体”返回。

attribute c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Bundle.columns的别名。

attribute columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

被此Bundle引用的 SQL 表达式的命名空间。

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

q = sess.query(bn).filter(bn.c.x == 5)

还支持 bundle 的嵌套:

b1 = Bundle("b1",
 Bundle('b2', MyClass.a, MyClass.b),
 Bundle('b3', MyClass.x, MyClass.y)
 )

q = sess.query(b1).filter(
 b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)

另请参阅

Bundle.c

method create_row_processor(query: Select[Any], procs: Sequence[Callable[[Row[Any]], Any]], labels: Sequence[str]) → Callable[[Row[Any]], Any]

为这个Bundle生成“行处理”函数。

可以被子类覆盖以在获取结果时提供自定义行为。该方法在查询执行时传递语句对象和一组“行处理”函数;这些处理函数在给定结果行时将返回单个属性值,然后可以将其调整为任何返回数据结构。

下面的示例说明了用直接的 Python 字典替换通常的Row返回结构:

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
 def create_row_processor(self, query, procs, labels):
 'Override create_row_processor to return values as
 dictionaries'

 def proc(row):
 return dict(
 zip(labels, (proc(row) for proc in procs))
 )
 return proc

上述Bundle的结果将返回字典值:

bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
 print(row.mybundle['data1'], row.mybundle['data2'])
attribute is_aliased_class = False

如果此对象是AliasedClass的实例,则为 True。

attribute is_bundle = True

如果此对象是Bundle的实例,则为 True。

attribute is_clause_element = False

如果此对象是ClauseElement的实例,则为 True。

attribute is_mapper = False

如果此对象是Mapper的实例,则为 True。

method label(name)

提供一个传递新标签的此Bundle的副本。

attribute single_entity = False

如果为 True,则单个 Bundle 的查询将作为单个实体返回,而不是作为键控元组中的元素。

function sqlalchemy.orm.with_loader_criteria(entity_or_base: _EntityType[Any], where_criteria: _ColumnExpressionArgument[bool] | Callable[[Any], _ColumnExpressionArgument[bool]], loader_only: bool = False, include_aliases: bool = False, propagate_to_loaders: bool = True, track_closure_variables: bool = True) → LoaderCriteriaOption

为特定实体的所有出现添加额外的 WHERE 条件加载。

从版本 1.4 开始新添加的功能。

with_loader_criteria()选项旨在向查询中的特定类型的实体全局添加限制条件,这意味着它将应用于实体在 SELECT 查询中的出现以及在任何子查询、联接条件和关系加载中,包括急切和延迟加载器,而无需在查询的任何特定部分指定它。渲染逻辑使用与单表继承相同的系统来确保某个鉴别器应用于表。

例如,使用 2.0 风格的查询,我们可以限制User.addresses集合的加载方式,而不管使用的加载类型如何:

from sqlalchemy.orm import with_loader_criteria

stmt = select(User).options(
 selectinload(User.addresses),
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

上述示例中,“selectinload”对于User.addresses将将给定的过滤条件应用于 WHERE 子句。

另一个示例,其中过滤将应用于联接的 ON 子句,在本示例中使用 1.x 风格的查询:

q = session.query(User).outerjoin(User.addresses).options(
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

with_loader_criteria()的主要目的是在SessionEvents.do_orm_execute()事件处理程序中使用它,以确保特定实体的所有出现以某种方式进行过滤,例如过滤访问控制角色。它还可用于应用于关系加载的条件。在下面的示例中,我们可以将一组特定规则应用于由特定Session发出的所有查询:

session = Session(bind=engine)

@event.listens_for("do_orm_execute", session)
def _add_filtering_criteria(execute_state):

 if (
 execute_state.is_select
 and not execute_state.is_column_load
 and not execute_state.is_relationship_load
 ):
 execute_state.statement = execute_state.statement.options(
 with_loader_criteria(
 SecurityRole,
 lambda cls: cls.role.in_(['some_role']),
 include_aliases=True
 )
 )

在上面的示例中,SessionEvents.do_orm_execute()事件将拦截使用Session发出的所有查询。对于那些是 SELECT 语句且不是属性或关系加载的查询,会向查询添加自定义的with_loader_criteria()选项。with_loader_criteria()选项将在给定的语句中使用,并将自动传播到所有从此查询下降的关系加载。

给定的 criteria 参数是一个接受cls参数的lambda。给定的类将扩展为包括所有映射的子类,而且本身不必是一个映射的类。

提示

当与contains_eager()加载选项一起使用with_loader_criteria()选项时,重要的是要注意with_loader_criteria()仅影响确定渲染的 SQL 的查询部分,涉及 WHERE 和 FROM 子句。contains_eager()选项不会影响 SELECT 语句的渲染,除了列子句外,因此与with_loader_criteria()选项没有任何交互。然而,“工作”的方式是,contains_eager()应该与某种方式已经从其他实体进行选择的查询一起使用,其中with_loader_criteria()可以应用其附加条件。

在下面的例子中,假设有一个映射关系如A -> A.bs -> B,给定的with_loader_criteria()选项将影响 JOIN 的渲染方式:

stmt = select(A).join(A.bs).options(
 contains_eager(A.bs),
 with_loader_criteria(B, B.flag == 1)
)

在上面的例子中,给定的with_loader_criteria()选项将影响由.join(A.bs)指定的 JOIN 的 ON 子句,因此会按预期应用。contains_eager()选项的效果是从B中添加列到列子句中:

SELECT
 b.id, b.a_id, b.data, b.flag,
 a.id AS id_1,
 a.data AS data_1
FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1

在上述语句中使用contains_eager()选项对with_loader_criteria()选项的行为没有影响。如果省略contains_eager()选项,则 SQL 与 FROM 和 WHERE 子句的行为相同,其中with_loader_criteria()继续将其条件添加到 JOIN 的 ON 子句中。contains_eager()的添加仅影响列子句,其中会添加针对b的额外列,然后 ORM 会使用它们生成B实例。

警告

在对with_loader_criteria()的调用中使用 lambda 表达式仅在每个唯一类中调用一次。自定义函数不应在此 lambda 内部调用。有关“lambda SQL”功能的概述,请参阅使用 Lambda 将显著提速到语句生成,该功能仅供高级用户使用。

参数:

  • entity_or_base - 映射类,或者一组特定映射类的超类,适用于规则的对象。

  • where_criteria -

    应用限制条件的核心 SQL 表达式。这也可以是一个接受目标类作为参数的“lambda:”或 Python 函数,当给定类是一个具有许多不同映射子类的基类时。

    注意

    为了支持 pickle,使用模块级 Python 函数生成 SQL 表达式,而不是 lambda 或固定的 SQL 表达式,后者往往不能 pickle 化。

  • include_aliases - 如果为 True,则将规则应用于aliased()构造。

  • propagate_to_loaders -

    默认为 True,适用于关系加载器,如延迟加载器。这表示选项对象本身包括 SQL 表达式随每个加载的实例一起传递。将其设置为False可防止将对象分配给单个实例。

    另请参见

    ORM 查询事件 - 包含使用with_loader_criteria()的示例。

    添加全局 WHERE / ON 条件 - 结合with_loader_criteria()SessionEvents.do_orm_execute()事件的基本示例。

  • track_closure_variables -

    当为 False 时,lambda 表达式内部的闭包变量将不会作为任何缓存键的一部分使用。这允许在 lambda 表达式内部使用更复杂的表达式,但要求 lambda 确保每次给定特定类时都返回相同的 SQL。

    新功能于版本 1.4.0b2 中添加。

function sqlalchemy.orm.join(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, isouter: bool = False, full: bool = False) → _ORMJoin

生成左右子句之间的内部连接。

join() 是对核心连接接口的扩展,由 join() 提供,其中左右可选择的对象不仅可以是核心可选择的对象,如 Table,还可以是映射类或 AliasedClass 实例。 “on” 子句可以是 SQL 表达式,也可以是引用已配置的 relationship() 的 ORM 映射属性。

在现代用法中,通常不常需要 join(),因为其功能已封装在 Select.join()Query.join() 方法中。 这些方法除了 join() 本身之外,还具有大量的自动化功能。 使用 ORM 启用的 SELECT 语句显式使用 join() 涉及使用 Select.select_from() 方法,如下所示:

from sqlalchemy.orm import join
stmt = select(User).\
 select_from(join(User, Address, User.addresses)).\
 filter(Address.email_address=='foo@bar.com')

在现代的 SQLAlchemy 中,上述连接可以更简洁地编写为:

stmt = select(User).\
 join(User.addresses).\
 filter(Address.email_address=='foo@bar.com')

警告

直接使用 join() 可能无法正确地处理现代 ORM 选项,例如 with_loader_criteria()。 强烈建议在创建 ORM 连接时使用像 Select.join()Select.join_from() 这样的方法提供的惯用连接模式。

另请参阅

连接 - 在 ORM 查询指南中了解惯用的 ORM 连接模式的背景

function sqlalchemy.orm.outerjoin(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, full: bool = False) → _ORMJoin

在左侧和右侧子句之间生成左外连接。

这是 join() 函数的“外连接”版本,具有相同的行为,除了生成 OUTER JOIN 外,还生成了其他用法详细信息,请参阅该函数的文档。

function sqlalchemy.orm.with_parent(instance: object, prop: attributes.QueryableAttribute[Any], from_entity: _EntityType[Any] | None = None) → ColumnElement[bool]

创建将此查询的主要实体与给定的相关实例相关联的过滤标准,使用已建立的 relationship() 配置。

例如:

stmt = select(Address).where(with_parent(some_user, User.addresses))

渲染的 SQL 与在给定父项上从该属性触发延迟加载时呈现的 SQL 相同,这意味着在 Python 中从父对象获取适当的状态而无需在呈现的语句中渲染对父表的连接。

给定的属性还可以使用 PropComparator.of_type() 来指示条件的左侧:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses.of_type(a2))
)

上述用法相当于使用 from_entity() 参数:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses, from_entity=a2)
)

参数:

  • instance – 具有某些 relationship() 的实例。

  • property – 类绑定的属性,指示应使用哪个实例的关系来协调父/子关系。

  • from_entity

    要考虑为左侧的实体。默认为 Query 本身的“零”实体。

    1.2 版中的新内容。

ORM Loader 选项

Loader 选项是对象,当传递给 Select.options() 方法时,影响了 Select 对象或类似的 SQL 结构的列和关系属性的加载。大多数 loader 选项都来自 Load 层次结构。有关使用 loader 选项的完整概述,请参阅下面的链接部分。

另请参阅

  • 列加载选项 - 详细介绍了影响如何加载列和 SQL 表达式映射属性的映射和加载选项

  • 关系加载技术 - 详细介绍了影响如何加载 relationship() 映射属性的关系和加载选项

ORM 执行选项

ORM 级别的执行选项是关键字选项,可以通过Session.execute.execution_options参数与语句执行关联,该参数是由Session方法(如Session.execute()Session.scalars())接受的字典参数,或者直接通过Executable.execution_options()方法将它们与要调用的语句直接关联,该方法将它们作为任意关键字参数接受。

ORM 级别的选项与Connection.execution_options()中记录的核心级别执行选项不同。需要注意的是,下面讨论的 ORM 选项与核心级别方法Connection.execution_options()Engine.execution_options() 兼容;即使EngineConnection与正在使用的Session相关联,这些选项在此级别将被忽略。

在本节中,将演示Executable.execution_options()方法的样式示例。

刷新现有数据

populate_existing执行选项确保对于加载的所有行,Session中对应的实例将被完全刷新 - 擦除对象中的任何现有数据(包括待定更改)并用从结果加载的数据替换。

示例用法如下:

>>> stmt = select(User).execution_options(populate_existing=True)
>>> result = session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
... 

通常,ORM 对象只加载一次,如果它们与后续结果行中的主键匹配,则不会将该行应用于对象。这既是为了保留对象上未决的未刷新更改,也是为了避免刷新已经存在的数据的开销和复杂性。 Session 假定高度隔离的事务的默认工作模型,并且在预期事务内部的数据发生更改的程度上,会使用明确的步骤来处理那些使用情况,而不是正在进行的本地更改。

使用 populate_existing,可以刷新与查询匹配的任何一组对象,并且还可以控制关系加载器选项。例如,刷新一个实例同时也刷新相关的一组对象:

stmt = (
    select(User)
    .where(User.name.in_(names))
    .execution_options(populate_existing=True)
    .options(selectinload(User.addresses))
)
# will refresh all matching User objects as well as the related
# Address objects
users = session.execute(stmt).scalars().all()

populate_existing 的另一个用例是支持各种属性加载功能,这些功能可以根据每个查询的情况改变属性的加载方式。适用于此选项的选项包括:

  • with_expression() 选项

  • PropComparator.and_() 方法可以修改加载器策略加载的内容

  • contains_eager() 选项

  • with_loader_criteria() 选项

  • load_only() 选项以选择要刷新的属性

populate_existing 执行选项等效于 1.x 风格 ORM 查询中的 Query.populate_existing() 方法。

另请参阅

我正在使用我的 Session 重新加载数据,但它没有看到我在其他地方提交的更改 - 在常见问题解答中

刷新/过期 - 在 ORM Session 文档中 ### 自动刷新

当传递此选项为 False 时,将导致 Session 不调用“自动刷新”步骤。这相当于使用 Session.no_autoflush 上下文管理器来禁用自动刷新:

>>> stmt = select(User).execution_options(autoflush=False)
>>> session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
... 

此选项还可用于启用 ORM 的 UpdateDelete 查询。

autoflush 执行选项相当于 1.x 风格 ORM 查询中的 Query.autoflush() 方法。

另请参阅

刷新 ### 使用 Yield Per 获取大型结果集

yield_per 执行选项是一个整数值,它将导致 Result 一次仅缓冲有限数量的行和/或 ORM 对象,然后将数据提供给客户端。

通常,ORM 会立即获取所有行,为每个构造 ORM 对象,并将这些对象组装到一个单一的缓冲区中,然后将该缓冲区作为行的来源传递给 Result 对象以返回。此行为的理由是允许正确处理诸如联接急加载、结果唯一化以及依赖于标识映射在每个对象在被提取时保持一致状态的结果处理逻辑等功能的情况。

yield_per 选项的目的是更改此行为,使得 ORM 结果集针对迭代大型结果集(例如 > 10K 行)进行了优化,用户已确定上述模式不适用的情况。当使用 yield_per 时,ORM 将会将 ORM 结果批量成子集合,并在迭代 Result 对象时单独从每个子集合中产生行,这样 Python 解释器就不需要声明非常大的内存区域,这既耗时又导致内存使用过多。该选项既影响数据库游标的使用方式,也影响 ORM 构造要传递给 Result 的行和对象的方式。

提示

由上可见,Result 必须以可迭代的方式消耗,即使用诸如 for row in result 的迭代或使用部分行方法,如 Result.fetchmany()Result.partitions()。调用 Result.all() 将失去使用 yield_per 的目的。

使用yield_per等同于同时利用Connection.execution_options.stream_results执行选项,该选项在支持的情况下选择使用后端的服务器端游标,并且在返回的Result对象上使用Result.yield_per()方法,该方法建立了要获取的行的固定大小以及一次构建多少个 ORM 对象的相应限制。

提示

yield_per现在也作为 Core 执行选项可用,详细描述在使用服务器端游标(即流式结果)。本节详细介绍了将yield_per作为 ORM Session的执行选项的用法。该选项在两种情境下尽可能相似地行为。

在与 ORM 一起使用时,yield_per必须通过给定语句上的Executable.execution_options()方法或通过将其传递给Session.execute()或其他类似Session方法的Session.execute.execution_options参数来建立。获取 ORM 对象的典型用法如下所示:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for user_obj in session.scalars(stmt):
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

上述代码等同于下面的示例,该示例使用Connection.execution_options.stream_resultsConnection.execution_options.max_row_buffer Core 级别的执行选项,与ResultResult.yield_per()方法一起使用:

# equivalent code
>>> stmt = select(User).execution_options(stream_results=True, max_row_buffer=10)
>>> for user_obj in session.scalars(stmt).yield_per(10):
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

yield_per通常与Result.partitions()方法结合使用,该方法将迭代分组分区中的行。每个分区的大小默认为传递给yield_per的整数值,如下例所示:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for partition in session.scalars(stmt).partitions():
...     for user_obj in partition:
...         print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

当使用集合时,yield_per执行选项与“子查询”急加载或“连接”急加载不兼容。对于“select in”急加载,只要数据库驱动程序支持多个独立游标,它就可能与之兼容。

另外,yield_per执行选项与Result.unique()方法不兼容;由于此方法依赖于存储所有行的完整标识集,它必然会破坏使用yield_per的目的,即处理任意数量的行。

自 1.4.6 版本更改:当从使用Result对象获取 ORM 行时,如果同时使用了Result.unique()过滤器和yield_per执行选项,则会引发异常。

在使用旧版 Query 对象和 1.x 样式 ORM 时,Query.yield_per() 方法的结果与yield_per执行选项的结果相同。

另请参阅

使用服务器端游标(又名流式结果) ### 标识令牌

深度炼金术

此选项是一个高级特性,主要用于与水平分片扩展一起使用。对于从不同“分片”或分区加载具有相同主键的对象的典型情况,请首先考虑每个分片使用单独的Session对象。

“标识令牌”是一个任意值,可以与新加载对象的标识键相关联。这个元素首先存在于支持每行“分片”的扩展中,其中对象可以从特定数据库表的任意数量的副本中加载,尽管这些副本具有重叠的主键值。 “标识令牌”的主要消费者是 Horizontal Sharding 扩展,它提供了一个��用框架,用于在特定数据库表的多个“分片”之间持久化对象。

identity_token执行选项可以在每个查询基础上直接影响此令牌的使用。直接使用它,可以将一个对象的多个实例填充到Session中,这些实例具有相同的主键和源表,但具有不同的“标识”。

一个这样的例子是使用 Schema 名称翻译功能,该功能可以影响查询范围内的模式选择,从而将来自不同模式的同名表中的对象填充到Session中。给定一个映射如下:

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

class Base(DeclarativeBase):
    pass

class MyTable(Base):
    __tablename__ = "my_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

上述类的默认“模式”名称为None,意味着不会将模式限定写入 SQL 语句中。然而,如果我们利用Connection.execution_options.schema_translate_map,将None映射到替代模式,我们可以将MyTable的实例放入两个不同的模式中:

engine = create_engine(
    "postgresql+psycopg://scott:tiger@localhost/test",
)

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema"})
) as sess:
    sess.add(MyTable(name="this is schema one"))
    sess.commit()

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema_2"})
) as sess:
    sess.add(MyTable(name="this is schema two"))
    sess.commit()

上述两个块每次创建一个与不同模式转换映射相关联的Session对象,并将MyTable的实例持久化到test_schema.my_tabletest_schema_2.my_table中。

上述Session对象是独立的。如果我们想要在一个事务中持久化这两个对象,我们需要使用 Horizontal Sharding 扩展来实现。

然而,我们可以在一个会话中演示查询这些对象的方法如下:

with Session(engine) as sess:
    obj1 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema"},
            identity_token="test_schema",
        )
    )
    obj2 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema_2"},
            identity_token="test_schema_2",
        )
    )

obj1obj2彼此不同。然而,它们都指向MyTable类的主键 id 1,但是它们是不同的。这就是identity_token发挥作用的地方,我们可以在每个对象的检查中看到它,在那里我们查看InstanceState.key以查看这两个不同的标识令牌:

>>> from sqlalchemy import inspect
>>> inspect(obj1).key
(<class '__main__.MyTable'>, (1,), 'test_schema')
>>> inspect(obj2).key
(<class '__main__.MyTable'>, (1,), 'test_schema_2')

当使用 Horizontal Sharding 扩展时,上述逻辑会自动发生。

2.0.0rc1 版本中的新功能:- 添加了identity_tokenORM 级别执行选项。

另请参阅

水平分片 - 在 ORM 示例 部分。请参阅脚本 separate_schema_translates.py,了解使用完整分片 API 进行上述用例演示。

检查启用 ORM 的 SELECT 和 DML 语句中的实体和列

select() 结构以及 insert()update()delete() 结构(自 SQLAlchemy 1.4.33 起,对于后三个 DML 结构)都支持检查这些语句所针对的实体,以及结果集中将返回的列和数据类型。

对于 Select 对象,此信息可以从 Select.column_descriptions 属性获取。该属性的操作方式与传统的 Query.column_descriptions 属性相同。返回的格式是一个字典列表:

>>> from pprint import pprint
>>> user_alias = aliased(User, name="user2")
>>> stmt = select(User, User.id, user_alias)
>>> pprint(stmt.column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'type': <class 'User'>},
 {'aliased': False,
 'entity': <class 'User'>,
 'expr': <....InstrumentedAttribute object at ...>,
 'name': 'id',
 'type': Integer()},
 {'aliased': True,
 'entity': <AliasedClass ...; User>,
 'expr': <AliasedClass ...; User>,
 'name': 'user2',
 'type': <class 'User'>}]

当与非 ORM 对象(如普通的 TableColumn 对象)一起使用 Select.column_descriptions 时,所有情况下都会包含有关返回的各个列的基本信息:

>>> stmt = select(user_table, address_table.c.id)
>>> pprint(stmt.column_descriptions)
[{'expr': Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
 'name': 'id',
 'type': Integer()},
 {'expr': Column('name', String(), table=<user_account>, nullable=False),
 'name': 'name',
 'type': String()},
 {'expr': Column('fullname', String(), table=<user_account>),
 'name': 'fullname',
 'type': String()},
 {'expr': Column('id', Integer(), table=<address>, primary_key=True, nullable=False),
 'name': 'id_1',
 'type': Integer()}]

自 1.4.33 版更改:当对未启用 ORM 的 Select 使用 Select.column_descriptions 属性时,现在会返回一个值。之前会引发 NotImplementedError

对于insert()update()delete()构造,存在两个单独的属性。一个是UpdateBase.entity_description,它返回关于 DML 构造将影响的主 ORM 实体和数据库表的信息:

>>> from sqlalchemy import update
>>> stmt = update(User).values(name="somename").returning(User.id)
>>> pprint(stmt.entity_description)
{'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'table': Table('user_account', ...),
 'type': <class 'User'>}

提示

UpdateBase.entity_description包括一个条目"table",实际上是由语句插入、更新或删除的,这与类可能映射到的 SQL“selectable”不一定相同。例如,在连接表继承场景中,"table"将引用给定实体的本地表。

另一个是UpdateBase.returning_column_descriptions,它以与Select.column_descriptions大致相似的方式提供了与 RETURNING 集合中存在的列有关的信息:

>>> pprint(stmt.returning_column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute ...>,
 'name': 'id',
 'type': Integer()}]

新版本 1.4.33 中增加了UpdateBase.entity_descriptionUpdateBase.returning_column_descriptions属性。 #### 额外的 ORM API 构造

对象名称 描述
aliased(element[, alias, name, flat, ...]) 生成给定元素的别名,通常是AliasedClass实例。
AliasedClass 代表用于 Query 的映射类的“别名”形式。
AliasedInsp AliasedClass对象提供检查接口。
Bundle 将由Query返回的 SQL 表达式分组到一个命名空间下。
join(left, right[, onclause, isouter, ...]) 生成左右子句之间的内连接。
outerjoin(left, right[, onclause, full]) 在左右子句之间生成左外连接。
with_loader_criteria(entity_or_base, where_criteria[, loader_only, include_aliases, ...]) 为特定实体的所有出现加载添加额外的 WHERE 条件。
with_parent(instance, prop[, from_entity]) 创建过滤条件,将此查询的主实体与给定的相关实例关联起来,使用已建立的relationship()配置。
function sqlalchemy.orm.aliased(element: _EntityType[_O] | FromClause, alias: FromClause | None = None, name: str | None = None, flat: bool = False, adapt_on_names: bool = False) → AliasedClass[_O] | FromClause | AliasedType[_O]

创建给定元素的别名,通常是AliasedClass实例。

例如:

my_alias = aliased(MyClass)

stmt = select(MyClass, my_alias).filter(MyClass.id > my_alias.id)
result = session.execute(stmt)

aliased()函数用于创建一个映射类到新的可选项的临时映射。默认情况下,通过通常的映射可选项(通常是Table)使用FromClause.alias()方法生成可选项。但是,aliased()也可以用于将类链接到新的select()语句。此外,with_polymorphic()函数是aliased()的变体,旨在指定所谓的“多态可选项”,该可选项对应于一次性联接继承子类的联合。

为了方便起见,aliased()函数也接受普通的FromClause构造,比如Tableselect()构造。在这些情况下,对象会调用FromClause.alias()方法,并返回新的Alias对象。在这种情况下,返回的Alias不会被 ORM 映射。

另请参阅

ORM 实体别名 - 在 SQLAlchemy 统一教程 中

选择 ORM 别名 - 在 ORM 查询指南 中

参数:

  • element – 要别名的元素。通常是一个映射类,但为了方便,也可以是一个FromClause元素。

  • alias – 可选的可选择单元,用于将元素映射到。通常用于将对象链接到子查询,并且应该是一个别名选择结构,就像从Query.subquery()方法或Select.subquery()Select.alias()方法生成的那样select()结构。

  • name – 如果未由alias参数指定,则使用的可选字符串名称。名称,除其他外,形成了通过Query对象返回的元组访问的属性名称。在创建Join对象的别名时不支持。

  • flat – 布尔值,将传递到FromClause.alias()调用,以便Join对象的别名将别名加入到连接内的单个表,而不是创建子查询。这通常由所有现代数据库支持,关于右嵌套连接,通常生成更有效的查询。

  • adapt_on_names

    如果为 True,则在将 ORM 实体的映射列映射到给定可选择的列时将使用更自由的“匹配” - 如果给定的可选择否则没有与实体上的列对应的列,则将执行基于名称的匹配。这种用例是当将实体与某个派生的可选择相关联时,例如使用聚合函数的可选择:

    class UnitPrice(Base):
     __tablename__ = 'unit_price'
     ...
     unit_id = Column(Integer)
     price = Column(Numeric)
    
    aggregated_unit_price = Session.query(
     func.sum(UnitPrice.price).label('price')
     ).group_by(UnitPrice.unit_id).subquery()
    
    aggregated_unit_price = aliased(UnitPrice,
     alias=aggregated_unit_price, adapt_on_names=True)
    

    在上面,对aggregated_unit_price上的函数引用.price将返回func.sum(UnitPrice.price).label('price')列,因为它根据名称“price”进行匹配。通常,“price”函数不会与实际的UnitPrice.price列有任何“列对应”,因为它不是原始列的代理。

class sqlalchemy.orm.util.AliasedClass

表示与查询一起使用的映射类的“别名”形式。

ORM 中 alias() 构造的等效对象,该对象使用 __getattr__ 方案模仿映射类,并维护对真实 Alias 对象的引用。

AliasedClass 的一个主要目的是在 ORM 生成的 SQL 语句中作为一个替代,以便一个现有的映射实体可以在多个上下文中使用。一个简单的例子:

# find all pairs of users with the same name
user_alias = aliased(User)
session.query(User, user_alias).\
 join((user_alias, User.id > user_alias.id)).\
 filter(User.name == user_alias.name)

AliasedClass 还能够将现有的映射类映射到一个全新的可选择项,前提是该可选择项与现有的映射可选择项兼容,并且还可以在映射中配置为 relationship() 的目标。请参见下面的链接以获取示例。

AliasedClass 对象通常使用 aliased() 函数构造。在使用 with_polymorphic() 函数时,还会进行额外配置。

结果对象是 AliasedClass 的一个实例。该对象实现了一个属性方案,产生与原始映射类相同的属性和方法接口,允许 AliasedClass 与在原始类上工作的任何属性技术兼容,包括混合属性(参见 混合属性)。

AliasedClass 可以通过 inspect() 进行检查,以获取其底层的 Mapper、别名可选择项和其他信息:

from sqlalchemy import inspect
my_alias = aliased(MyClass)
insp = inspect(my_alias)

结果检查对象是 AliasedInsp 的一个实例。

另请参阅

aliased()

with_polymorphic()

与别名类的关系

使用窗口函数限制行关系

类签名

sqlalchemy.orm.AliasedClass (sqlalchemy.inspection.Inspectable, sqlalchemy.orm.ORMColumnsClauseRole)

class sqlalchemy.orm.util.AliasedInsp

AliasedClass对象提供检查接口。

给定一个AliasedClass,使用inspect()函数将返回一个AliasedInsp对象:

from sqlalchemy import inspect
from sqlalchemy.orm import aliased

my_alias = aliased(MyMappedClass)
insp = inspect(my_alias)

AliasedInsp上的属性包括:

  • entity - 表示的AliasedClass

  • mapper - 映射底层类的Mapper

  • selectable - 最终表示别名TableSelect构造的Alias构造。

  • name - 别名的名称。 当从Query中的结果元组返回时,也用作属性名称。

  • with_polymorphic_mappers - 指示在 AliasedClass 的 select 构造中表达的所有这些映射器的集合。

  • polymorphic_on - 一个备用的列或 SQL 表达式,将用作多态加载的“辨别器”。

另请参阅

运行时检查 API

类签名

sqlalchemy.orm.AliasedInsp (sqlalchemy.orm.ORMEntityColumnsClauseRole, sqlalchemy.orm.ORMFromClauseRole, sqlalchemy.sql.cache_key.HasCacheKey, sqlalchemy.orm.base.InspectionAttr, sqlalchemy.util.langhelpers.MemoizedSlots, sqlalchemy.inspection.Inspectable, typing.Generic)

class sqlalchemy.orm.Bundle

Query在一个命名空间下返回的 SQL 表达式的分组。

Bundle基本上允许对列导向的Query对象返回的基于元组的结果进行嵌套。 它还可以通过简单的子类化进行扩展,其中要覆盖的主要能力是如何返回表达式集,允许后处理以及自定义返回类型,而不涉及 ORM 身份映射的类。

另请参阅

使用 Bundles 分组选择的属性

成员

init(), c, columns, create_row_processor(), is_aliased_class, is_bundle, is_clause_element, is_mapper, label(), single_entity

类签名

sqlalchemy.orm.Bundle (sqlalchemy.orm.ORMColumnsClauseRole, sqlalchemy.sql.annotation.SupportsCloneAnnotations, sqlalchemy.sql.cache_key.MemoizedHasCacheKey, sqlalchemy.inspection.Inspectable, sqlalchemy.orm.base.InspectionAttr)。

method __init__(name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any)

构造一个新的Bundle

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

for row in session.query(bn).filter(
 bn.c.x == 5).filter(bn.c.y == 4):
 print(row.mybundle.x, row.mybundle.y)

参数:

  • name – bundle 的名称。

  • *exprs – 组成 bundle 的列或 SQL 表达式。

  • single_entity=False – 如果为 True,则此Bundle的行可以作为“单个实体”返回,而不是在与映射实体相同的元组中。

attribute c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Bundle.columns的别名。

attribute columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

被此Bundle引用的 SQL 表达式的命名空间。

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

q = sess.query(bn).filter(bn.c.x == 5)

也支持 bundle 的嵌套:

b1 = Bundle("b1",
 Bundle('b2', MyClass.a, MyClass.b),
 Bundle('b3', MyClass.x, MyClass.y)
 )

q = sess.query(b1).filter(
 b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)

另请参阅

Bundle.c

method create_row_processor(query: Select[Any], procs: Sequence[Callable[[Row[Any]], Any]], labels: Sequence[str]) → Callable[[Row[Any]], Any]

为此Bundle生成“行处理”函数。

可以被子类覆盖以在获取结果时提供自定义行为。该方法在查询执行时传递给语句对象和一组“行处理”函数;这些处理函数在给定结果行时将返回单个属性值,然后可以将其调整为任何返回数据结构。

下面的示例说明了将通常的Row返回结构替换为直接的 Python 字典:

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
 def create_row_processor(self, query, procs, labels):
 'Override create_row_processor to return values as
 dictionaries'

 def proc(row):
 return dict(
 zip(labels, (proc(row) for proc in procs))
 )
 return proc

上述Bundle的结果将返回字典值:

bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
 print(row.mybundle['data1'], row.mybundle['data2'])
attribute is_aliased_class = False

如果此对象是AliasedClass的实例,则为 True。

attribute is_bundle = True

如果此对象是Bundle的实例,则为 True。

attribute is_clause_element = False

如果此对象是ClauseElement的实例,则为 True。

attribute is_mapper = False

如果此对象是Mapper的实例,则为 True。

method label(name)

提供一个传递了新标签的 Bundle 的副本。

attribute single_entity = False

如果为 True,则对于单个 Bundle 的查询将返回为单个实体,而不是在一个键元组中的元素。

function sqlalchemy.orm.with_loader_criteria(entity_or_base: _EntityType[Any], where_criteria: _ColumnExpressionArgument[bool] | Callable[[Any], _ColumnExpressionArgument[bool]], loader_only: bool = False, include_aliases: bool = False, propagate_to_loaders: bool = True, track_closure_variables: bool = True) → LoaderCriteriaOption

为所有特定实体的加载添加额外的 WHERE 条件。

新版本 1.4 中的新功能。

with_loader_criteria() 选项旨在向查询中的特定类型的实体添加限制条件,全局地,这意味着它将应用于实体在 SELECT 查询中的出现方式以及任何子查询、连接条件和关系加载中,包括急切加载和延迟加载器,而无需在查询的任何特定部分中指定它。呈现逻辑使用与单表继承相同的系统来确保某个鉴别器应用于表。

例如,使用 2.0 样式 查询,我们可以限制 User.addresses 集合的加载方式,而不管所使用的加载类型是什么:

from sqlalchemy.orm import with_loader_criteria

stmt = select(User).options(
 selectinload(User.addresses),
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

上面,“selectinload” 对于 User.addresses 将把给定的过滤条件应用于 WHERE 子句。

另一个示例,其中过滤将应用于连接的 ON 子句,在此示例中使用 1.x 样式 查询:

q = session.query(User).outerjoin(User.addresses).options(
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

with_loader_criteria() 的主要目的是在 SessionEvents.do_orm_execute() 事件处理程序中使用它,以确保对特定实体的所有出现方式都以某种方式进行过滤,例如针对访问控制角色的过滤。它还可以用于将条件应用于关系加载。在下面的示例中,我们可以将一定的规则应用于特定 Session 发出的所有查询:

session = Session(bind=engine)

@event.listens_for("do_orm_execute", session)
def _add_filtering_criteria(execute_state):

 if (
 execute_state.is_select
 and not execute_state.is_column_load
 and not execute_state.is_relationship_load
 ):
 execute_state.statement = execute_state.statement.options(
 with_loader_criteria(
 SecurityRole,
 lambda cls: cls.role.in_(['some_role']),
 include_aliases=True
 )
 )

在上面的示例中,SessionEvents.do_orm_execute() 事件将拦截使用 Session 发出的所有查询。对于那些是 SELECT 语句且不是属性或关系加载的查询,会向查询中添加自定义 with_loader_criteria() 选项。with_loader_criteria() 选项将在给定的语句中使用,并将自动传播到所有从该查询继承的关系加载。

给定的 criteria 参数是一个接受 cls 参数的 lambda。给定的类将扩展为包括所有映射的子类,它本身不需要是一个映射的类。

提示

在与contains_eager()加载选项一起使用with_loader_criteria()选项时,重要的是要注意with_loader_criteria()仅影响决定渲染的 SQL 的部分查询,即 WHERE 和 FROM 子句。contains_eager()选项不影响 SELECT 语句在列子句之外的渲染,因此与with_loader_criteria()选项没有任何交互。然而,事情的“运作方式”是,contains_eager()旨在与某种方式已经从附加实体中进行选择的查询一起使用,其中with_loader_criteria()可以应用其附加条件。

在下面的示例中,假设有一个映射关系为A -> A.bs -> B,给定的with_loader_criteria()选项将影响 JOIN 的呈现方式:

stmt = select(A).join(A.bs).options(
 contains_eager(A.bs),
 with_loader_criteria(B, B.flag == 1)
)

给定的with_loader_criteria()选项将影响由.join(A.bs)指定的 JOIN 的 ON 子句,因此被如预期般应用。contains_eager()选项的作用是将 B 的列添加到列子句中:

SELECT
 b.id, b.a_id, b.data, b.flag,
 a.id AS id_1,
 a.data AS data_1
FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1

在上述语句中使用contains_eager()选项对with_loader_criteria()选项的行为没有影响。如果省略了contains_eager()选项,那么 SQL 在 FROM 和 WHERE 子句方面的情况将与with_loader_criteria()继续将其条件添加到 JOIN 的 ON 子句中一样。contains_eager()的添加仅影响列子句,即添加了针对 b 的附加列,然后 ORM 将其消耗以产生 B 实例。

警告

在调用with_loader_criteria()时使用的 lambda 仅会被每个唯一类调用一次。自定义函数不应在此 lambda 内部调用。有关“lambda SQL”功能的概述,请参阅使用 Lambda 将语句生成速度提升到显著水平,该功能仅适用于高级用途。

参数:

  • entity_or_base - 一个映射类,或者是一组特定映射类的超类,适用于该规则。

  • where_criteria -

    一个应用限制条件的核心 SQL 表达式。当给定类是具有许多不同映射子类的基类时,这也可以是“lambda:”或 Python 函数,它接受目标类作为参数。

    注意

    为了支持 pickle,应使用模块级别的 Python 函数来生成 SQL 表达式,而不是 lambda 或固定的 SQL 表达式,后者倾向于不可 pickle。

  • include_aliases - 如果为 True,则也将规则应用于aliased()构造。

  • propagate_to_loaders -

    默认为 True,适用于诸如延迟加载器之类的关系加载器。这表示选项对象本身包括 SQL 表达式与每个加载的实例一起传递。将其设置为False可防止将对象分配给单个实例。

    另请参阅

    ORM 查询事件 - 包括使用with_loader_criteria()的示例。

    添加全局 WHERE / ON 条件 - 将with_loader_criteria()SessionEvents.do_orm_execute()事件相结合的基本示例。

  • track_closure_variables -

    当 False 时,lambda 表达式内部的闭包变量将不会用作任何缓存键的一部分。这允许在 lambda 表达式内部使用更复杂的表达式,但需要 lambda 确保每次给定特定类时都返回相同的 SQL。

    从版本 1.4.0b2 开始新增。

function sqlalchemy.orm.join(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, isouter: bool = False, full: bool = False) → _ORMJoin

生成左右子句之间的内部连接。

join() 是对由 join() 提供的核心连接接口的扩展,其中左右可选择的对象不仅可以是核心可选择对象(如 Table),还可以是映射类或 AliasedClass 实例。"on" 子句可以是 SQL 表达式,也可以是引用已配置的 relationship() 的 ORM 映射属性。

join() 在现代用法中不常用,因为其功能已封装在 Select.join()Query.join() 方法中。这些方法的自动化程度远远超出了 join() 本身。在启用 ORM 的 SELECT 语句中明确使用 join(),需要使用 Select.select_from() 方法,示例如下:

from sqlalchemy.orm import join
stmt = select(User).\
 select_from(join(User, Address, User.addresses)).\
 filter(Address.email_address=='foo@bar.com')

在现代 SQLAlchemy 中,以上连接可以更简洁地写为:

stmt = select(User).\
 join(User.addresses).\
 filter(Address.email_address=='foo@bar.com')

警告

直接使用 join() 可能无法与现代 ORM 选项(如 with_loader_criteria())正常工作。强烈建议在创建 ORM 连接时使用诸如 Select.join()Select.join_from() 等方法提供的惯用连接模式。

另请参阅

连接 - 在 ORM 查询指南中了解惯用 ORM 连接模式的背景知识

function sqlalchemy.orm.outerjoin(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, full: bool = False) → _ORMJoin

在左右子句之间产生一个左外连接。

这是 join() 函数的“外连接”版本,功能与其相同,只是生成了一个 OUTER JOIN。请参阅该函数的文档以获取其他用法细节。

function sqlalchemy.orm.with_parent(instance: object, prop: attributes.QueryableAttribute[Any], from_entity: _EntityType[Any] | None = None) → ColumnElement[bool]

创建将此查询的主实体与给定的相关实例相关联的过滤条件,使用已建立的 relationship() 配置。

例如:

stmt = select(Address).where(with_parent(some_user, User.addresses))

渲染的 SQL 与在给定父对象上的属性上触发惰性加载器时渲染的 SQL 相同,这意味着适当的状态从 Python 中的父对象中获取,而不需要在渲染的语句中渲染到父表的连接。

给定的属性也可以使用 PropComparator.of_type() 来指示条件的左侧:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses.of_type(a2))
)

上述用法等同于使用 from_entity() 参数:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses, from_entity=a2)
)

参数:

  • instance - 具有一些 relationship() 的实例。

  • property - 类绑定属性,指示应从实例使用哪个关系来协调父/子关系。

  • from_entity -

    要考虑为左侧的实体。这默认为 Query 本身的“零”实体。

    版本 1.2 中的新功能。### Populate Existing

populate_existing 执行选项确保,对于加载的所有行,Session 中对应的实例将被完全刷新 - 擦除对象中的任何现有数据(包括未决更改),并用从结果加载的数据替换。

示例用法如下:

>>> stmt = select(User).execution_options(populate_existing=True)
>>> result = session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
... 

通常,ORM 对象只加载一次,如果它们与后续结果行中的主键匹配,那么该行不会应用于对象。这既是为了保留对象上未决的未刷新更改,也是为了避免刷新已经存在的数据的开销和复杂性。Session 假定高度隔离的事务的默认工作模型,并且在事务中预计数据会在本地更改之外发生变化的程度上,这些用例将使用显式步骤来处理,例如这种方法。

使用 populate_existing,任何与查询匹配的对象集合都可以刷新,并且还允许控制关系加载器选项。例如,刷新一个实例同时刷新一组相关对象:

stmt = (
    select(User)
    .where(User.name.in_(names))
    .execution_options(populate_existing=True)
    .options(selectinload(User.addresses))
)
# will refresh all matching User objects as well as the related
# Address objects
users = session.execute(stmt).scalars().all()

populate_existing 的另一个用例是支持各种属性加载功能,可以根据每个查询的情况更改如何加载属性。适用于此选项的选项包括:

  • with_expression() 选项

  • PropComparator.and_() 方法可以修改加载策略加载的内容

  • contains_eager() 选项

  • with_loader_criteria()选项

  • load_only()选项用于选择要刷新的属性

populate_existing执行选项等同于 1.x 风格 ORM 查询中的Query.populate_existing()方法。

另请参阅

我正在使用我的 Session 重新加载数据,但它没有看到我在其他地方提交的更改 - 在常见问题解答中

刷新/过期 - 在 ORM Session 文档中

自动刷新

当传递为False时,此选项将导致Session不调用“autoflush”步骤。这相当于使用Session.no_autoflush上下文管理器来禁用自动刷新:

>>> stmt = select(User).execution_options(autoflush=False)
>>> session.execute(stmt)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
... 

此选项也适用于启用 ORM 的UpdateDelete查询。

autoflush执行选项等同于 1.x 风格 ORM 查询中的Query.autoflush()方法。

另请参阅

刷新

使用 Yield Per 获取大型结果集

yield_per执行选项是一个整数值,它将导致Result一次仅缓冲有限数量的行和/或 ORM 对象,然后再将数据提供给客户端。

通常,ORM 会立即获取所有行,为每一行构建 ORM 对象,并将这些对象组装到一个单一缓冲区中,然后将此缓冲区传递给Result对象作为要返回的行的来源。这种行为的理由是为了允许诸如连接的急切加载、结果的唯一化以及依赖于标识映射在获取时为结果集中的每个对象保持一致状态的结果处理逻辑等功能的正确行为。

yield_per 选项的目的是改变这种行为,使得 ORM 结果集对于迭代非常大的结果集(例如 > 10K 行)进行了优化,其中用户已确定上述模式不适用。当使用 yield_per 时,ORM 将把 ORM 结果分批到子集合中,并在迭代 Result 对象时,从每个子集合中分别产生行,这样 Python 解释器就不需要声明非常大的内存区域,这既耗时又导致内存使用过多。该选项影响数据库游标的使用方式,以及 ORM 构造行和对象以传递给 Result 的方式。

提示

由上可见,Result 必须以可迭代的方式被消耗,即使用迭代(如 for row in result)或使用部分行方法(如 Result.fetchmany()Result.partitions())。调用 Result.all() 将使使用 yield_per 的目的失败。

使用 yield_per 相当于同时利用了 Connection.execution_options.stream_results 执行选项,如果支持的话,选择使用后端使用服务器端游标,并且在返回的 Result 对象上使用 Result.yield_per() 方法,它建立了要提取的行的固定大小,以及一次构造多少个 ORM 对象的相应限制。

提示

yield_per 现在也作为一个 Core 执行选项可用,详细描述请参阅 使用服务器端游标(又名流式结果)。本节详细介绍了将 yield_per 作为 ORM Session 的执行选项使用的方法。该选项在两种情境下尽可能地行为相似。

当与 ORM 一起使用时,yield_per 必须通过给定语句上的 Executable.execution_options() 方法或通过将其传递给 Session.execute() 或其他类似的 Session 方法的 Session.execute.execution_options 参数来建立。如下是用于获取 ORM 对象的典型用法:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for user_obj in session.scalars(stmt):
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

上述代码等同于下面的示例,该示例使用了 Connection.execution_options.stream_resultsConnection.execution_options.max_row_buffer 核心级别的执行选项,结合 Result.yield_per() 方法:

# equivalent code
>>> stmt = select(User).execution_options(stream_results=True, max_row_buffer=10)
>>> for user_obj in session.scalars(stmt).yield_per(10):
...     print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

yield_per 也常与 Result.partitions() 方法结合使用,该方法将在分组分区中迭代行。每个分区的大小默认为传递给 yield_per 的整数值,如下例所示:

>>> stmt = select(User).execution_options(yield_per=10)
>>> for partition in session.scalars(stmt).partitions():
...     for user_obj in partition:
...         print(user_obj)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
...
>>> # ... rows continue ...

yield_per 执行选项不兼容于使用集合时的 “子查询”急切加载 或 “连接”急切加载。如果数据库驱动程序支持多个独立的游标,则它可能与 “select in”急切加载 兼容。

此外,yield_per 执行选项不兼容于 Result.unique() 方法;因为该方法依赖于为所有行存储完整的标识集,这必然会破坏使用 yield_per 的目的,即处理任意大量的行。

在版本 1.4.6 中更改:当使用Result对象获取 ORM 行时,如果同时使用Result.unique()过滤器以及yield_per执行选项,则会引发异常。

当使用传统的Query对象进行 1.x style ORM 使用时,Query.yield_per()方法将与yield_per执行选项具有相同的结果。

另请参阅

使用服务器端游标(又名流式结果)

身份令牌

深度炼金术

此选项是一个高级使用功能,主要用于与 Horizontal Sharding 扩展一起使用。对于从不同“shards”或分区加载具有相同主键的对象的典型情况,请首先考虑每个“shard”使用单独的Session对象。

“身份令牌”是一个任意值,可以与新加载对象的 identity key 相关联。此元素首先存在以支持执行按行“sharding”的扩展,其中对象可以从特定数据库表的任何数量的副本中加载,尽管它们具有重叠的主键值。 “身份令牌”的主要消费者是 Horizontal Sharding 扩展,它提供了一种在特定数据库表的多个“shards”之间持久化对象的通用框架。

identity_token执行选项可以根据每个查询直接影响此令牌。直接使用它,可以填充一个Session的多个对象实例,这些对象具有相同的主键和来源表,但具有不同的“身份”。

其中一个示例是使用 Schema Names 的翻译功能来填充一个Session,该功能可以影响查询范围内架构的选择,对象来自不同模式中的同名表。给定映射如下:

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

class Base(DeclarativeBase):
    pass

class MyTable(Base):
    __tablename__ = "my_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

上述类的默认“模式”名称为 None,意味着不会在 SQL 语句中写入模式限定符。但是,如果我们使用Connection.execution_options.schema_translate_map,将 None 映射到另一个模式,我们可以将 MyTable 的实例放入两个不同的模式中:

engine = create_engine(
    "postgresql+psycopg://scott:tiger@localhost/test",
)

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema"})
) as sess:
    sess.add(MyTable(name="this is schema one"))
    sess.commit()

with Session(
    engine.execution_options(schema_translate_map={None: "test_schema_2"})
) as sess:
    sess.add(MyTable(name="this is schema two"))
    sess.commit()

上述两个代码块创建一个Session对象,每次链接到不同的模式转换映射,并且 MyTable 的实例被持久化到 test_schema.my_tabletest_schema_2.my_table

上述的 Session 对象是独立的。如果我们想要在一个事务中持久化这两个对象,我们需要使用 水平分片 扩展来执行此操作。

然而,我们可以在一个会话中演示查询这些对象的方法如下:

with Session(engine) as sess:
    obj1 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema"},
            identity_token="test_schema",
        )
    )
    obj2 = sess.scalar(
        select(MyTable)
        .where(MyTable.id == 1)
        .execution_options(
            schema_translate_map={None: "test_schema_2"},
            identity_token="test_schema_2",
        )
    )

obj1obj2 都彼此不同。但是,它们都指向 MyTable 类的主键 id 1,但是不同。这就是 identity_token 起作用的方式,我们可以在每个对象的检查中看到,其中我们查看 InstanceState.key 来查看两个不同的身份令牌:

>>> from sqlalchemy import inspect
>>> inspect(obj1).key
(<class '__main__.MyTable'>, (1,), 'test_schema')
>>> inspect(obj2).key
(<class '__main__.MyTable'>, (1,), 'test_schema_2')

上述逻辑在使用 水平分片 扩展时会自动进行。

从版本 2.0.0rc1 开始新增: - 添加了 identity_token ORM 层执行选项。

另请参阅

水平分片 - 在 ORM 示例 部分。查看脚本 separate_schema_translates.py,演示了使用完整分片 API 的上述用例。

从启用 ORM 的 SELECT 和 DML 语句中检查实体和列

select() 构造,以及 insert()update()delete() 构造(对于后两个 DML 构造,在 SQLAlchemy 1.4.33 中),都支持检查创建这些语句的实体,以及在结果集中返回的列和数据类型的能力。

对于Select对象,此信息可从Select.column_descriptions属性中获取。此属性的操作方式与传统的Query.column_descriptions属性相同。返回的格式是一个字典列表:

>>> from pprint import pprint
>>> user_alias = aliased(User, name="user2")
>>> stmt = select(User, User.id, user_alias)
>>> pprint(stmt.column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'type': <class 'User'>},
 {'aliased': False,
 'entity': <class 'User'>,
 'expr': <....InstrumentedAttribute object at ...>,
 'name': 'id',
 'type': Integer()},
 {'aliased': True,
 'entity': <AliasedClass ...; User>,
 'expr': <AliasedClass ...; User>,
 'name': 'user2',
 'type': <class 'User'>}]

Select.column_descriptions与非 ORM 对象一起使用,比如普通的TableColumn对象时,所有情况下返回的条目将包含关于各个列的基本信息:

>>> stmt = select(user_table, address_table.c.id)
>>> pprint(stmt.column_descriptions)
[{'expr': Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
 'name': 'id',
 'type': Integer()},
 {'expr': Column('name', String(), table=<user_account>, nullable=False),
 'name': 'name',
 'type': String()},
 {'expr': Column('fullname', String(), table=<user_account>),
 'name': 'fullname',
 'type': String()},
 {'expr': Column('id', Integer(), table=<address>, primary_key=True, nullable=False),
 'name': 'id_1',
 'type': Integer()}]

1.4.33 版本中的更改:当针对未启用 ORM 的Select使用时,Select.column_descriptions属性现在会返回一个值。以前,这会引发NotImplementedError

对于insert()update()delete()构造,有两个单独的属性。一个是UpdateBase.entity_description,它返回有关主要 ORM 实体和数据库表的信息,该信息会受到 DML 构造的影响:

>>> from sqlalchemy import update
>>> stmt = update(User).values(name="somename").returning(User.id)
>>> pprint(stmt.entity_description)
{'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'table': Table('user_account', ...),
 'type': <class 'User'>}

提示

UpdateBase.entity_description包括一个条目"table",它实际上是该语句要插入、更新或删除的,这并不总是与类可能被映射到的 SQL“selectable”相同。例如,在联接表继承场景中,"table"将引用给定实体的本地表。

另一个是 UpdateBase.returning_column_descriptions,它以与 Select.column_descriptions 类似的方式提供有关 RETURNING 集合中存在的列的信息:

>>> pprint(stmt.returning_column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute ...>,
 'name': 'id',
 'type': Integer()}]

新版本 1.4.33 中新增:添加了 UpdateBase.entity_descriptionUpdateBase.returning_column_descriptions 属性。 #### 附加的 ORM API 构造

对象名称 描述
aliased(element[, alias, name, flat, ...]) 生成给定元素的别名,通常是一个 AliasedClass 实例。
AliasedClass 代表一个与查询一起使用的映射类的“别名”形式。
AliasedInsp AliasedClass 对象提供检查接口。
Bundle 一个由查询返回的 SQL 表达式的分组,位于一个命名空间下。
join(left, right[, onclause, isouter, ...]) 生成左右子句之间的内连接。
outerjoin(left, right[, onclause, full]) 生成左外连接 left 和 right 之间的连接。
with_loader_criteria(entity_or_base, where_criteria[, loader_only, include_aliases, ...]) 为特定实体的所有出现加载时添加额外的 WHERE 条件。
with_parent(instance, prop[, from_entity]) 创建过滤条件,将此查询的主要实体与给定的相关实例相关联,使用已建立的 relationship() 配置。
function sqlalchemy.orm.aliased(element: _EntityType[_O] | FromClause, alias: FromClause | None = None, name: str | None = None, flat: bool = False, adapt_on_names: bool = False) → AliasedClass[_O] | FromClause | AliasedType[_O]

生成给定元素的别名,通常是一个 AliasedClass 实例。

例如:

my_alias = aliased(MyClass)

stmt = select(MyClass, my_alias).filter(MyClass.id > my_alias.id)
result = session.execute(stmt)

aliased() 函数用于创建映射类到新可选择项的临时映射。默认情况下,从通常映射的可选择项(通常是一个 Table )使用 FromClause.alias() 方法生成可选择项。然而,aliased() 还可以用于将类链接到新的 select() 语句。此外,with_polymorphic() 函数是 aliased() 的变体,旨在指定所谓的“多态可选择项”,它对应于一次性联接继承子类的联合。

为了方便起见,aliased() 函数还接受纯粹的FromClause 构造,比如Tableselect() 构造。在这些情况下,调用对象的 FromClause.alias() 方法,并返回新的 Alias 对象。在这种情况下,返回的 Alias 对象不是 ORM 映射的。

参见

ORM 实体别名 - 在 SQLAlchemy 统一教程

选择 ORM 别名 - 在 ORM 查询指南

参数:

  • element – 要别名的元素。通常是一个映射类,但为了方便,也可以是一个FromClause 元素。

  • alias - 可选的可选择单元,将元素映射到该单元。这通常用于将对象链接到子查询,并且应该是从 Query.subquery() 方法或 Select.subquery()Select.alias() 方法以及 select() 构造的结果中生成的别名选择构造。

  • name - 如果未由 alias 参数指定,则用于别名的可选字符串名称。名称,除其他外,形成了通过 Query 对象返回的元组可访问的属性名。不支持创建 Join 对象的别名时使用。

  • flat - 布尔值,将传递给 FromClause.alias() 调用,以便 Join 对象的别名将别名内部的各个表,而不是创建子查询。这通常由所有现代数据库支持,关于右嵌套连接,通常会生成更有效的查询。

  • adapt_on_names -

    如果为 True,则在将 ORM 实体的映射列映射到给定可选择的列时,将使用更自由的“匹配” - 如果给定可选择的没有与实体上的列对应的列,则将执行基于名称的匹配。这种情况的用例是将实体与一些派生的可选择相关联,例如使用聚合函数的可选择:

    class UnitPrice(Base):
     __tablename__ = 'unit_price'
     ...
     unit_id = Column(Integer)
     price = Column(Numeric)
    
    aggregated_unit_price = Session.query(
     func.sum(UnitPrice.price).label('price')
     ).group_by(UnitPrice.unit_id).subquery()
    
    aggregated_unit_price = aliased(UnitPrice,
     alias=aggregated_unit_price, adapt_on_names=True)
    

    对于 aggregated_unit_price 上的函数,引用 .price 的将返回 func.sum(UnitPrice.price).label('price') 列,因为它与名称“price”匹配。通常,“price”函数不会与实际的 UnitPrice.price 列具有任何“列对应关系”,因为它不是原始列的代理。

class sqlalchemy.orm.util.AliasedClass

表示映射类的“别名”形式,用于与查询一起使用。

ORM 等效于 alias() 构造的对象,该对象使用 __getattr__ 方案模拟映射类,并维护对实际 Alias 对象的引用。

AliasedClass 的一个主要目的是在 ORM 生成的 SQL 语句中作为一个替代,以便一个现有的映射实体可以在多个上下文中使用。一个简单的例子:

# find all pairs of users with the same name
user_alias = aliased(User)
session.query(User, user_alias).\
 join((user_alias, User.id > user_alias.id)).\
 filter(User.name == user_alias.name)

AliasedClass 还能够将一个现有的映射类映射到一个全新的可选项,前提是该可选项与现有的映射可选项兼容,并且还可以在映射中配置为 relationship() 的目标。请参考下面的链接获取示例。

AliasedClass 对象通常使用 aliased() 函数构建。在使用 with_polymorphic() 函数时,还可以进行附加配置。

结果对象是 AliasedClass 的一个实例。该对象实现了一个属性方案,生成与原始映射类相同的属性和方法接口,使得 AliasedClass 可与在原始类上有效的任何属性技术兼容,包括混合属性(参见 混合属性)。

AliasedClass 可以通过 inspect() 进行检查,以获取其底层的 Mapper、别名可选项等信息:

from sqlalchemy import inspect
my_alias = aliased(MyClass)
insp = inspect(my_alias)

检查结果对象是 AliasedInsp 的一个实例。

另请参见

aliased()

with_polymorphic()

与别名类的关系

使用窗口函数限制行关系

类签名

sqlalchemy.orm.AliasedClass (sqlalchemy.inspection.Inspectable, sqlalchemy.orm.ORMColumnsClauseRole)

class sqlalchemy.orm.util.AliasedInsp

AliasedClass 对象提供检查接口。

给定AliasedClass,使用inspect()函数返回AliasedInsp对象:

from sqlalchemy import inspect
from sqlalchemy.orm import aliased

my_alias = aliased(MyMappedClass)
insp = inspect(my_alias)

AliasedInsp的属性包括:

  • entity - 表示的AliasedClass

  • mapper - 映射底层类的Mapper

  • selectable - 最终表示别名TableSelect构造的Alias构造。

  • name - 别名的名称。当从Query返回结果元组时,也用作属性名称。

  • with_polymorphic_mappers - 指示选择构造中表示所有这些映射的Mapper对象集合,用于AliasedClass

  • polymorphic_on - 用作多态加载的“鉴别器”的备用列或 SQL 表达式。

另请参阅

运行时检查 API

类签名

sqlalchemy.orm.AliasedInsp (sqlalchemy.orm.ORMEntityColumnsClauseRole, sqlalchemy.orm.ORMFromClauseRole, sqlalchemy.sql.cache_key.HasCacheKey, sqlalchemy.orm.base.InspectionAttr, sqlalchemy.util.langhelpers.MemoizedSlots, sqlalchemy.inspection.Inspectable, typing.Generic)

class sqlalchemy.orm.Bundle

由一个命名空间下的Query返回的 SQL 表达式分组。

Bundle基本上允许通过简单的子类化来嵌套由基于列的Query对象返回的基于元组的结果。它还可以通过简单的子类化来扩展,其中要重写的主要功能是如何返回表达式集,允许进行后处理以及自定义返回类型,而无需涉及 ORM 身份映射的类。

另请参阅

使用 Bundle 对选定的属性进行分组

成员

init(), c, columns, create_row_processor(), is_aliased_class, is_bundle, is_clause_element, is_mapper, label(), single_entity

类签名

class sqlalchemy.orm.Bundle (sqlalchemy.orm.ORMColumnsClauseRole, sqlalchemy.sql.annotation.SupportsCloneAnnotations, sqlalchemy.sql.cache_key.MemoizedHasCacheKey, sqlalchemy.inspection.Inspectable, sqlalchemy.orm.base.InspectionAttr)

method __init__(name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any)

构建一个新的 Bundle

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

for row in session.query(bn).filter(
 bn.c.x == 5).filter(bn.c.y == 4):
 print(row.mybundle.x, row.mybundle.y)

参数:

  • name – bundle 的名称。

  • *exprs – 组成 bundle 的列或 SQL 表达式。

  • single_entity=False – 如果为 True,则此 Bundle 的行可以作为“单个实体”返回,方式与映射实体相同,不在任何封闭元组之外。

attribute c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Bundle.columns 的别名。

attribute columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Bundle 所引用的 SQL 表达式的命名空间。

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

q = sess.query(bn).filter(bn.c.x == 5)

还支持 bundle 的嵌套:

b1 = Bundle("b1",
 Bundle('b2', MyClass.a, MyClass.b),
 Bundle('b3', MyClass.x, MyClass.y)
 )

q = sess.query(b1).filter(
 b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)

另请参阅

Bundle.c

method create_row_processor(query: Select[Any], procs: Sequence[Callable[[Row[Any]], Any]], labels: Sequence[str]) → Callable[[Row[Any]], Any]

生成此 Bundle 的“行处理”函数。

可以被子类重写以在获取结果时提供自定义行为。该方法在查询执行时传递了语句对象和一组“行处理器”函数;这些处理器函数在给定结果行时将返回单个属性值,然后可以将其适应为任何类型的返回数据结构。

下面的示例说明了将通常的 Row 返回结构替换为直接的 Python 字典:

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
 def create_row_processor(self, query, procs, labels):
 'Override create_row_processor to return values as
 dictionaries'

 def proc(row):
 return dict(
 zip(labels, (proc(row) for proc in procs))
 )
 return proc

上述 Bundle 的结果将返回字典值:

bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
 print(row.mybundle['data1'], row.mybundle['data2'])
attribute is_aliased_class = False

如果此对象是 AliasedClass 的实例,则为 True。

attribute is_bundle = True

如果此对象是 Bundle 的实例,则为 True。

attribute is_clause_element = False

如果此对象是 ClauseElement 的实例,则为 True。

attribute is_mapper = False

如果此对象是 Mapper 的实例,则为 True。

method label(name)

提供此 Bundle 的副本并传递一个新标签。

attribute single_entity = False

如果为 True,则单个 Bundle 的查询将作为单个实体返回,而不是作为键元组中的元素。

function sqlalchemy.orm.with_loader_criteria(entity_or_base: _EntityType[Any], where_criteria: _ColumnExpressionArgument[bool] | Callable[[Any], _ColumnExpressionArgument[bool]], loader_only: bool = False, include_aliases: bool = False, propagate_to_loaders: bool = True, track_closure_variables: bool = True) → LoaderCriteriaOption

为特定实体的所有出现添加额外的 WHERE 条件。

在 1.4 版本中新增。

with_loader_criteria()选项旨在向查询中的特定实体添加限制条件,全局地应用于实体在 SELECT 查询中的出现以及任何子查询、连接条件和关系加载中,包括急切加载和延迟加载器,而无需在查询的任何特定部分指定它。渲染逻辑使用与单表继承相同的系统,以确保某个鉴别器应用于表。

例如,使用 2.0 风格的查询,我们可以限制User.addresses集合的加载方式,无论使用何种加载方式:

from sqlalchemy.orm import with_loader_criteria

stmt = select(User).options(
 selectinload(User.addresses),
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

上述对User.addresses的“selectinload”将把给定的过滤条件应用于 WHERE 子句。

另一个例子,过滤将应用于连接的 ON 子句,在这个例子中使用 1.x 风格的查询:

q = session.query(User).outerjoin(User.addresses).options(
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

with_loader_criteria()的主要目的是在SessionEvents.do_orm_execute()事件处理程序中使用它,以确保以某种方式过滤特定实体的所有出现,例如过滤访问控制角色。它还可以用于应用关系加载的条件。在下面的例子中,我们可以对特定Session发出的所有查询应用一定的规则:

session = Session(bind=engine)

@event.listens_for("do_orm_execute", session)
def _add_filtering_criteria(execute_state):

 if (
 execute_state.is_select
 and not execute_state.is_column_load
 and not execute_state.is_relationship_load
 ):
 execute_state.statement = execute_state.statement.options(
 with_loader_criteria(
 SecurityRole,
 lambda cls: cls.role.in_(['some_role']),
 include_aliases=True
 )
 )

在上面的例子中,SessionEvents.do_orm_execute()事件将拦截使用Session发出的所有查询。对于那些是 SELECT 语句且不是属性或关系加载的查询,将为查询添加自定义的with_loader_criteria()选项。with_loader_criteria()选项将用于给定语句,并将自动传播到所有从此查询派生的关系加载。

给定的 criteria 参数是一个接受cls参数的lambda。给定的类将扩展以包括所有映射的子类,本身不必是一个映射的类。

提示

当与with_loader_criteria()选项一起使用时,需要注意with_loader_criteria()仅影响查询中确定渲染的 SQL 的部分,即 WHERE 和 FROM 子句。contains_eager()选项不会影响 SELECT 语句的渲染,除了列子句外的其他部分,因此与with_loader_criteria()选项没有任何交互。然而,“工作”的方式是contains_eager()旨在与已经以某种方式从其他实体进行选择的查询一起使用,而with_loader_criteria()可以应用其额外的条件。

在下面的示例中,假设一个映射关系为A -> A.bs -> B,给定的with_loader_criteria()选项将影响 JOIN 的渲染方式:

stmt = select(A).join(A.bs).options(
 contains_eager(A.bs),
 with_loader_criteria(B, B.flag == 1)
)

在上面的例子中,给定的with_loader_criteria()选项将影响由.join(A.bs)指定的 JOIN 的 ON 子句,因此会按预期应用。contains_eager()选项会导致B的列被添加到列子句中:

SELECT
 b.id, b.a_id, b.data, b.flag,
 a.id AS id_1,
 a.data AS data_1
FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1

在上述语句中使用contains_eager()选项对with_loader_criteria()选项的行为没有影响。如果省略contains_eager()选项,则 SQL 将与 FROM 和 WHERE 子句相关,而with_loader_criteria()将继续将其条件添加到 JOIN 的 ON 子句中。添加contains_eager()仅会影响列子句,即会添加对b的额外列,然后 ORM 会使用这些列来生成B实例。

警告

在调用 with_loader_criteria() 内部的 lambda 中,每个唯一类只调用一次 lambda。自定义函数不应在此 lambda 内部调用。有关“lambda SQL”功能的概述,请参阅使用 Lambda 为语句生成添加显著的速度增益,该功能仅供高级使用。

参数:

  • entity_or_base – 一个映射类,或者是一组特定映射类的超类,规则将适用于这些类。

  • where_criteria

    一个核心 SQL 表达式,应用限制条件。当给定类是具有许多不同映射子类的基类时,这也可以是“lambda:”或 Python 函数,接受目标类作为参数。

    注意

    为了支持 pickling,使用模块级 Python 函数来生成 SQL 表达式,而不是 lambda 或固定的 SQL 表达式,后者往往不可 picklable。

  • include_aliases – 如果为 True,则也将规则应用于 aliased() 构造。

  • propagate_to_loaders

    默认为 True,适用于关系加载器,例如延迟加载器。这表示选项对象本身,包括 SQL 表达式,将与每个加载的实例一起传递。设置为 False 以防止对象分配给各个实例。

    另请参阅

    ORM 查询事件 - 包括使用 with_loader_criteria() 的示例。

    添加全局 WHERE / ON 条件 - 如何将 with_loader_criteria()SessionEvents.do_orm_execute() 事件结合的基本示例。

  • track_closure_variables

    当 False 时,lambda 表达式内部的闭包变量不会用作任何缓存键的一部分。这允许在 lambda 表达式内部使用更复杂的表达式,但需要 lambda 确保每次给定特定类时返回相同的 SQL。

    新版本 1.4.0b2 中新增。

function sqlalchemy.orm.join(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, isouter: bool = False, full: bool = False) → _ORMJoin

生成左右子句之间的内部连接。

join()是对join()提供的核心连接接口的扩展,其中左右可选择的对象不仅可以是核心可选择的对象,如Table,还可以是映射类或AliasedClass实例。"on"子句可以是 SQL 表达式,也可以是引用已配置的relationship()的 ORM 映射属性。

join() 在现代用法中通常不需要,因为其功能已经封装在Select.join()Query.join()方法中。这些方法比单独使用join()具有更多的自动化功能。在启用 ORM 的 SELECT 语句中显式使用join(),需要使用Select.select_from()方法,如下所示:

from sqlalchemy.orm import join
stmt = select(User).\
 select_from(join(User, Address, User.addresses)).\
 filter(Address.email_address=='foo@bar.com')

在现代的 SQLAlchemy 中,上述连接可以更简洁地编写为:

stmt = select(User).\
 join(User.addresses).\
 filter(Address.email_address=='foo@bar.com')

警告

直接使用join()可能无法与现代 ORM 选项(如with_loader_criteria())正常工作。强烈建议在创建 ORM 连接时使用诸如Select.join()Select.join_from()等方法提供的惯用连接模式。

另请参阅

连接 - 了解 ORM 连接模式的背景知识

function sqlalchemy.orm.outerjoin(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, full: bool = False) → _ORMJoin

生成左外连接(left outer join)左边和右边子句之间的关联。

这是join()函数的“外连接”版本,具有相同的行为,只是生成了 OUTER JOIN。请参阅该函数的文档以获取其他用法细节。

function sqlalchemy.orm.with_parent(instance: object, prop: attributes.QueryableAttribute[Any], from_entity: _EntityType[Any] | None = None) → ColumnElement[bool]

创建过滤条件,将此查询的主实体与给定的相关实例相关联,使用已建立的relationship()配置。

例如:

stmt = select(Address).where(with_parent(some_user, User.addresses))

渲染的 SQL 与在给定父对象上的惰性加载程序触发时所渲染的 SQL 相同,这意味着在 Python 中从父对象中取得适当的状态而无需将父表的联接渲染到渲染的语句中。

给定的属性也可以使用 PropComparator.of_type() 来指示条件的左侧:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses.of_type(a2))
)

上述用法等同于使用 from_entity() 参数:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses, from_entity=a2)
)

参数:

  • instance – 一个具有一些 relationship() 的实例。

  • property – 类绑定属性,表示应该使用实例中的哪种关系来协调父/子关系。

  • from_entity

    要考虑为左侧的实体。默认为 Query 本身的“零”实体。

    新版 1.2 中新增。#### 从 ORM 启用的 SELECT 和 DML 语句中检查实体和列

select() 构造,以及 insert()update()delete() 构造(对于后两个 DML 构造,在 SQLAlchemy 1.4.33 中),都支持检查这些语句所针对的实体,以及将在结果集中返回的列和数据类型。

对于 Select 对象,此信息可从 Select.column_descriptions 属性获得。此属性的操作方式与传统的 Query.column_descriptions 属性相同。返回的格式是一个字典列表:

>>> from pprint import pprint
>>> user_alias = aliased(User, name="user2")
>>> stmt = select(User, User.id, user_alias)
>>> pprint(stmt.column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'type': <class 'User'>},
 {'aliased': False,
 'entity': <class 'User'>,
 'expr': <....InstrumentedAttribute object at ...>,
 'name': 'id',
 'type': Integer()},
 {'aliased': True,
 'entity': <AliasedClass ...; User>,
 'expr': <AliasedClass ...; User>,
 'name': 'user2',
 'type': <class 'User'>}]

Select.column_descriptions 与非 ORM 对象一起使用,如普通的 TableColumn 对象时,条目将在所有情况下包含有关返回的各个列的基本信息:

>>> stmt = select(user_table, address_table.c.id)
>>> pprint(stmt.column_descriptions)
[{'expr': Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
 'name': 'id',
 'type': Integer()},
 {'expr': Column('name', String(), table=<user_account>, nullable=False),
 'name': 'name',
 'type': String()},
 {'expr': Column('fullname', String(), table=<user_account>),
 'name': 'fullname',
 'type': String()},
 {'expr': Column('id', Integer(), table=<address>, primary_key=True, nullable=False),
 'name': 'id_1',
 'type': Integer()}]

版本 1.4.33 中的更改:当用于未启用 ORM 的 Select 时,Select.column_descriptions 属性现在返回一个值。先前,这会引发 NotImplementedError

对于insert()update()delete() 构造,存在两个单独的属性。一个是UpdateBase.entity_description,它返回有关 DML 构造将影响的主要 ORM 实体和数据库表的信息:

>>> from sqlalchemy import update
>>> stmt = update(User).values(name="somename").returning(User.id)
>>> pprint(stmt.entity_description)
{'entity': <class 'User'>,
 'expr': <class 'User'>,
 'name': 'User',
 'table': Table('user_account', ...),
 'type': <class 'User'>}

Tip

UpdateBase.entity_description 包括一个条目 "table",实际上是语句要插入、更新或删除的,这通常与类可能被映射到的 SQL "selectable" 相同。例如,在联合表继承场景中,"table" 将引用给定实体的本地表。

另一个是 UpdateBase.returning_column_descriptions,它以一种与Select.column_descriptions大致相似的方式提供了有关 RETURNING 集合中存在的列的信息:

>>> pprint(stmt.returning_column_descriptions)
[{'aliased': False,
 'entity': <class 'User'>,
 'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute ...>,
 'name': 'id',
 'type': Integer()}]

版本 1.4.33 中的新内容:增加了 UpdateBase.entity_descriptionUpdateBase.returning_column_descriptions 属性。

其他 ORM API 构造

对象名称 描述
aliased(element[, alias, name, flat, ...]) 生成给定元素的别名,通常是 AliasedClass 实例。
AliasedClass 代表映射类的“别名”形式,可用于查询。
AliasedInsp AliasedClass对象提供检查接口。
Bundle 由一个命名空间下的一个Query返回的 SQL 表达式组合。
join(left, right[, onclause, isouter, ...]) 在左右子句之间产生内连接。
outerjoin(left, right[, onclause, full]) 在左右子句之间生成左外连接。
with_loader_criteria(entity_or_base, where_criteria[, loader_only, include_aliases, ...]) 为特定实体的所有出现添加额外的 WHERE 条件以加载。
with_parent(instance, prop[, from_entity]) 创建过滤条件,将此查询的主要实体与给定的相关实例关联起来,使用已建立的relationship()配置。
function sqlalchemy.orm.aliased(element: _EntityType[_O] | FromClause, alias: FromClause | None = None, name: str | None = None, flat: bool = False, adapt_on_names: bool = False) → AliasedClass[_O] | FromClause | AliasedType[_O]

生成给定元素的别名,通常是一个AliasedClass实例。

例如:

my_alias = aliased(MyClass)

stmt = select(MyClass, my_alias).filter(MyClass.id > my_alias.id)
result = session.execute(stmt)

aliased()函数用于创建将映射类映射到新可选择项的临时映射。 默认情况下,可选择项是使用FromClause.alias()方法从通常映射的可选择项(通常是Table)生成的。但是,aliased()也可以用于将类链接到新的select()语句。 此外,with_polymorphic()函数是aliased()的变体,旨在指定所谓的“多态可选择项”,该可选择项对应于一次性联接继承子类的联合。

为方便起见,aliased() 函数还接受普通的FromClause构造,例如Tableselect()构造。在这些情况下,该对象上调用FromClause.alias()方法,并返回新的Alias对象。在这种情况下,返回的Alias不是 ORM 映射的。

另请参阅

ORM 实体别名 - 在 SQLAlchemy 统一教程中

选择 ORM 别名 - 在 ORM 查询指南中

参数:

  • element - 要别名化的元素。通常是一个映射的类,但出于方便起见,也可以是一个FromClause元素。

  • alias - 可选的可选择单元,将元素映射到该单元。这通常用于将对象链接到子查询,并且应该是一个别名选择构造,就像从Query.subquery()方法或Select.subquery()Select.alias()方法从select()构造中产生的那样。

  • name - 可选的字符串名称,用于别名,如果未由alias参数指定。该名称,除其他外,形成了由Query对象返回的元组访问的属性名称。在创建Join对象的别名时不受支持。

  • flat – 布尔值,将传递给FromClause.alias()调用,以便将Join对象的别名别名为加入其中的各个表,而不是创建子查询。这通常由所有现代数据库支持,关于右嵌套连接通常会产生更有效的查询。

  • adapt_on_names

    如果为 True,则在将 ORM 实体的映射列与给定可选择的列进行映射时将使用更宽松的“匹配” - 如果给定的可选择没有与实体上的列对应的列,则将执行基于名称的匹配。这种用例是当将实体与一些派生可选择关联时,例如使用聚合函数的可选择:

    class UnitPrice(Base):
     __tablename__ = 'unit_price'
     ...
     unit_id = Column(Integer)
     price = Column(Numeric)
    
    aggregated_unit_price = Session.query(
     func.sum(UnitPrice.price).label('price')
     ).group_by(UnitPrice.unit_id).subquery()
    
    aggregated_unit_price = aliased(UnitPrice,
     alias=aggregated_unit_price, adapt_on_names=True)
    

    上面,对aggregated_unit_price上的函数引用.price将返回func.sum(UnitPrice.price).label('price')列,因为它与名称“price”匹配。通常情况下,“price”函数不会与实际的UnitPrice.price列有任何“列对应”,因为它不是原始列的代理。

class sqlalchemy.orm.util.AliasedClass

表示用于与查询一起使用的映射类的“别名”形式。

ORM 中的alias()构造的等价物,此对象使用__getattr__方案模仿映射类,并维护对真实Alias对象的引用。

AliasedClass的一个主要目的是在 ORM 生成的 SQL 语句中作为一个替代品,使得现有的映射实体可以在多个上下文中使用。一个简单的例子:

# find all pairs of users with the same name
user_alias = aliased(User)
session.query(User, user_alias).\
 join((user_alias, User.id > user_alias.id)).\
 filter(User.name == user_alias.name)

AliasedClass还能够将现有的映射类映射到一个全新的可选择项,只要此可选择项与现有的映射可选择项兼容,并且它还可以被配置为relationship()的目标。请参阅下面的链接获取示例。

AliasedClass对象通常使用aliased()函数构造。当使用with_polymorphic()函数时,还会使用附加配置生成该对象。

结果对象是 AliasedClass 的一个实例。此对象实现了与原始映射类相同的属性和方法接口,允许 AliasedClass 兼容任何在原始类上工作的属性技术,包括混合属性(参见混合属性)。

可以使用 inspect() 检查 AliasedClass 的底层 Mapper、别名可选项和其他信息:

from sqlalchemy import inspect
my_alias = aliased(MyClass)
insp = inspect(my_alias)

结果检查对象是 AliasedInsp 的一个实例。

另请参阅

aliased()

with_polymorphic()

与别名类的关系

带窗口函数的行限制关系

类签名

sqlalchemy.orm.AliasedClasssqlalchemy.inspection.Inspectablesqlalchemy.orm.ORMColumnsClauseRole

class sqlalchemy.orm.util.AliasedInsp

AliasedClass 对象提供检查接口。

使用 inspect() 函数给定 AliasedClass 返回 AliasedInsp 对象:

from sqlalchemy import inspect
from sqlalchemy.orm import aliased

my_alias = aliased(MyMappedClass)
insp = inspect(my_alias)

AliasedInsp 上的属性包括:

  • entity - 代表的 AliasedClass

  • mapper - Mapper 映射了底层类。

  • selectable - 最终表示别名的 Alias 构造或 Select 构造。

  • name - 别名的名称。当从 Query 中的结果元组中返回时,也用作属性名称。

  • with_polymorphic_mappers - 包含表示选择结构中所有那些表示的 Mapper 对象的集合,用于 AliasedClass

  • polymorphic_on - 作为多态加载的“鉴别器”使用的替代列或 SQL 表达式。

另请参见

运行时检测 API

类签名

sqlalchemy.orm.AliasedInsp (sqlalchemy.orm.ORMEntityColumnsClauseRole, sqlalchemy.orm.ORMFromClauseRole, sqlalchemy.sql.cache_key.HasCacheKey, sqlalchemy.orm.base.InspectionAttr, sqlalchemy.util.langhelpers.MemoizedSlots, sqlalchemy.inspection.Inspectable, typing.Generic)

class sqlalchemy.orm.Bundle

一组由 Query 返回的 SQL 表达式,在一个命名空间下。

Bundle 实质上允许嵌套列导向 Query 对象返回的基于元组的结果。它还可以通过简单的子类扩展,其中主要的重写功能是如何返回表达式集,允许后处理以及自定义返回类型,而不涉及 ORM 身份映射类。

另请参见

使用 Bundles 分组选择的属性

成员

init(), c, columns, create_row_processor(), is_aliased_class, is_bundle, is_clause_element, is_mapper, label(), single_entity

类签名

sqlalchemy.orm.Bundle (sqlalchemy.orm.ORMColumnsClauseRole, sqlalchemy.sql.annotation.SupportsCloneAnnotations, sqlalchemy.sql.cache_key.MemoizedHasCacheKey, sqlalchemy.inspection.Inspectable, sqlalchemy.orm.base.InspectionAttr)

method __init__(name: str, *exprs: _ColumnExpressionArgument[Any], **kw: Any)

构造一个新的 Bundle

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

for row in session.query(bn).filter(
 bn.c.x == 5).filter(bn.c.y == 4):
 print(row.mybundle.x, row.mybundle.y)

参数:

  • name – bundle 的名称。

  • *exprs – 组成 bundle 的列或 SQL 表达式。

  • single_entity=False – 如果为 True,则此 Bundle 的行可以像映射实体一样在任何封闭元组之外返回。

attribute c: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Bundle.columns 的别名。

attribute columns: ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]

Bundle 引用的 SQL 表达式的命名空间。

例如:

bn = Bundle("mybundle", MyClass.x, MyClass.y)

q = sess.query(bn).filter(bn.c.x == 5)

支持嵌套捆绑:

b1 = Bundle("b1",
 Bundle('b2', MyClass.a, MyClass.b),
 Bundle('b3', MyClass.x, MyClass.y)
 )

q = sess.query(b1).filter(
 b1.c.b2.c.a == 5).filter(b1.c.b3.c.y == 9)

请参见

Bundle.c

method create_row_processor(query: Select[Any], procs: Sequence[Callable[[Row[Any]], Any]], labels: Sequence[str]) → Callable[[Row[Any]], Any]

生成此 Bundle 的“行处理”函数。

可以被子类覆盖以在获取结果时提供自定义行为。 方法在查询执行时传递语句对象和一组“行处理”函数;给定结果行时,这些处理函数将返回单个属性值,然后可以将其调整为任何类型的返回数据结构。

下面的示例说明了用直接的 Python 字典替换通常的 Row 返回结构:

from sqlalchemy.orm import Bundle

class DictBundle(Bundle):
 def create_row_processor(self, query, procs, labels):
 'Override create_row_processor to return values as
 dictionaries'

 def proc(row):
 return dict(
 zip(labels, (proc(row) for proc in procs))
 )
 return proc

上述 Bundle 的结果将返回字典值:

bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
 print(row.mybundle['data1'], row.mybundle['data2'])
attribute is_aliased_class = False

如果此对象是 AliasedClass 的实例,则为 True。

attribute is_bundle = True

如果此对象是 Bundle 的实例,则为 True。

attribute is_clause_element = False

如果此对象是 ClauseElement 的实例,则为 True。

attribute is_mapper = False

如果此对象是 Mapper 的实例,则为 True。

method label(name)

提供此 Bundle 的副本并传递一个新的标签。

attribute single_entity = False

如果为 True,则查询单个 Bundle 将返回单个实体,而不是键入元组中的元素。

function sqlalchemy.orm.with_loader_criteria(entity_or_base: _EntityType[Any], where_criteria: _ColumnExpressionArgument[bool] | Callable[[Any], _ColumnExpressionArgument[bool]], loader_only: bool = False, include_aliases: bool = False, propagate_to_loaders: bool = True, track_closure_variables: bool = True) → LoaderCriteriaOption

为特定实体的所有出现添加额外的 WHERE 条件到加载中。

版本 1.4 中的新功能。

with_loader_criteria() 选项旨在向查询中的特定类型的实体添加限制条件,全局,这意味着它将应用于实体在 SELECT 查询中出现的方式以及在任何子查询、连接条件和关系加载中,包括急切加载和惰性加载,而无需在查询的任何特定部分指定它。 渲染逻辑使用与单表继承相同的系统来确保某个特定的鉴别器应用于表。

例如,使用 2.0 样式 查询,我们可以限制 User.addresses 集合的加载方式,而不管使用的加载类型:

from sqlalchemy.orm import with_loader_criteria

stmt = select(User).options(
 selectinload(User.addresses),
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

上述中,“selectinload” 用于 User.addresses 将应用给定的过滤条件到 WHERE 子句。

另一个示例,其中过滤将应用于连接的 ON 子句,在此示例中使用 1.x 样式 查询:

q = session.query(User).outerjoin(User.addresses).options(
 with_loader_criteria(Address, Address.email_address != 'foo'))
)

with_loader_criteria()的主要目的是在SessionEvents.do_orm_execute()事件处理程序中使用它,以确保特定实体的所有出现都以某种方式被过滤,例如,为访问控制角色过滤。它还可以用于应用条件于关系加载。在下面的示例中,我们可以将一组特定规则应用于特定Session发出的所有查询:

session = Session(bind=engine)

@event.listens_for("do_orm_execute", session)
def _add_filtering_criteria(execute_state):

 if (
 execute_state.is_select
 and not execute_state.is_column_load
 and not execute_state.is_relationship_load
 ):
 execute_state.statement = execute_state.statement.options(
 with_loader_criteria(
 SecurityRole,
 lambda cls: cls.role.in_(['some_role']),
 include_aliases=True
 )
 )

在上面的示例中,SessionEvents.do_orm_execute()事件将拦截使用Session发出的所有查询。对于那些是 SELECT 语句且不是属性或关系加载的查询,将为查询添加一个自定义的with_loader_criteria()选项。with_loader_criteria()选项将在给定语句中使用,并且还将自动传播到所有从此查询继承的关系加载中。

给定的 criteria 参数是一个接受 cls 参数的lambda。给定的类将扩展以包括所有映射的子类,本身不需要是映射的类。

提示

当在与contains_eager()加载器选项一起使用with_loader_criteria()选项时,重要的是要注意,with_loader_criteria()仅影响决定以何种方式呈现 SQL 的查询的部分,这涉及 WHERE 和 FROM 子句。 contains_eager()选项不影响 SELECT 语句在列之外的呈现,因此与with_loader_criteria()选项没有任何交互。但是,事情的“工作”方式是contains_eager()应该与某种已经选择额外实体的查询一起使用,而with_loader_criteria()可以应用其额外的条件。

在下面的示例中,假设映射关系为 A -> A.bs -> B,给定的 with_loader_criteria() 选项将影响 JOIN 的呈现方式:

stmt = select(A).join(A.bs).options(
 contains_eager(A.bs),
 with_loader_criteria(B, B.flag == 1)
)

在上面的示例中,给定的 with_loader_criteria() 选项将影响由 .join(A.bs) 指定的 JOIN 的 ON 子句,因此按预期应用。contains_eager() 选项的效果是将 B 的列添加到列子句中:

SELECT
 b.id, b.a_id, b.data, b.flag,
 a.id AS id_1,
 a.data AS data_1
FROM a JOIN b ON a.id = b.a_id AND b.flag = :flag_1

在上述语句中使用 contains_eager() 选项对 with_loader_criteria() 选项的行为没有影响。如果省略了 contains_eager() 选项,则 SQL 将与 FROM 和 WHERE 子句相同,其中 with_loader_criteria() 继续将其条件添加到 JOIN 的 ON 子句中。添加 contains_eager() 只影响列子句,即添加了针对 B 的其他列,然后 ORM 消耗这些列以生成 B 实例。

警告

在对 with_loader_criteria() 的调用内部使用 lambda 只会 对每个唯一类调用一次。自定义函数不应在此 lambda 内调用。有关“lambda SQL”功能的概述,请参阅使用 Lambdas 为语句生成带来显著速度提升,这仅供高级使用。

参数:

  • entity_or_base – 映射类,或者是一组特定映射类的超类,将应用规则到其中。

  • where_criteria

    核心 SQL 表达式,应用限制条件。当给定类是一个具有许多不同映射子类的基类时,这也可以是一个“lambda:”或 Python 函数,接受目标类作为参数。

    注意

    为了支持 pickle,使用模块级 Python 函数生成 SQL 表达式,而不是 lambda 或固定的 SQL 表达式,后者往往不可 picklable。

  • include_aliases – 如果为 True,则也将规则应用于 aliased() 构造。

  • propagate_to_loaders

    默认为 True,适用于关系加载器,如惰性加载器。这表示选项对象本身,包括 SQL 表达式,将随每个加载的实例一起传递。将其设置为 False 可防止将对象分配给各个实例。

    另请参阅

    ORM 查询事件 - 包括使用 with_loader_criteria() 的示例。

    添加全局 WHERE / ON 条件 - 如何将 with_loader_criteria()SessionEvents.do_orm_execute() 事件结合的基本示例。

  • track_closure_variables -

    当为 False 时,lambda 表达式中的闭包变量将不会作为任何缓存键的一部分。这允许在 lambda 表达式中使用更复杂的表达式,但要求 lambda 确保每次给定特定类时返回相同的 SQL。

    新版本 1.4.0b2 中新增。

function sqlalchemy.orm.join(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, isouter: bool = False, full: bool = False) → _ORMJoin

在左右子句之间产生内连接。

join() 是对 join() 提供的核心连接接口的扩展,其中左右可选择的对象不仅可以是核心可选择对象,如 Table,还可以是映射类或 AliasedClass 实例。"on" 子句可以是 SQL 表达式,也可以是引用已配置的 relationship() 的 ORM 映射属性。

在现代用法中,通常不常用 join(),因为其功能已封装在 Select.join()Query.join() 方法中。这两种方法在自动化方面远远超出了 join() 本身。在启用 ORM 的 SELECT 语句中显式使用 join() 涉及使用 Select.select_from() 方法,如下所示:

from sqlalchemy.orm import join
stmt = select(User).\
 select_from(join(User, Address, User.addresses)).\
 filter(Address.email_address=='foo@bar.com')

在现代 SQLAlchemy 中,上述连接可以更简洁地写为:

stmt = select(User).\
 join(User.addresses).\
 filter(Address.email_address=='foo@bar.com')

警告

直接使用join()可能无法与现代 ORM 选项(如with_loader_criteria())正常工作。强烈建议在创建 ORM 连接时使用由Select.join()Select.join_from()等方法提供的成语连接模式。

另请参阅

连接 - 在 ORM 查询指南中了解成语连接模式的背景

function sqlalchemy.orm.outerjoin(left: _FromClauseArgument, right: _FromClauseArgument, onclause: _OnClauseArgument | None = None, full: bool = False) → _ORMJoin

在左侧和右侧子句之间产生左外连接。

这是join()函数的“外连接”版本,具有相同的行为,只是生成了一个外连接。有关其他用法细节,请参阅该函数的文档。

function sqlalchemy.orm.with_parent(instance: object, prop: attributes.QueryableAttribute[Any], from_entity: _EntityType[Any] | None = None) → ColumnElement[bool]

创建过滤条件,将此查询的主实体与给定的相关实例关联起来,使用已建立的relationship()配置。

例如:

stmt = select(Address).where(with_parent(some_user, User.addresses))

渲染的 SQL 与在给定父对象上的惰性加载器触发时渲染的 SQL 相同,这意味着在 Python 中从父对象中获取适当的状态而无需在渲染语句中渲染到父表的连接。

给定属性还可以利用PropComparator.of_type()指示条件的左侧:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses.of_type(a2))
)

上述用法等同于使用from_entity()参数:

a1 = aliased(Address)
a2 = aliased(Address)
stmt = select(a1, a2).where(
 with_parent(u1, User.addresses, from_entity=a2)
)

参数:

  • instance – 具有某些relationship()的实例。

  • property – 类绑定属性,指示应使用实例中的哪个关系来协调父/子关系。

  • from_entity

    要考虑为左侧的实体。默认为Query本身的“零”实体。

    版本 1.2 中的新功能。

posted @ 2024-06-22 11:33  绝不原创的飞龙  阅读(62)  评论(0编辑  收藏  举报