SqlAlchemy 2.0 中文文档(十八)(1)

简介: SqlAlchemy 2.0 中文文档(十八)


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

列加载选项

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

关于本文档

本节介绍了有关加载列的其他选项。使用的映射包括将存储大字符串值的列,我们可能希望限制它们何时加载。

查看此页面的 ORM 设置。以下示例中的一些将重新定义 Book 映射器以修改某些列定义。

使用列推迟限制加载的列

列推迟 指的是在查询该类型的对象时,从 SELECT 语句中省略的 ORM 映射列。这里的一般原理是性能,在表中具有很少使用的列,并且具有潜在的大数据值,因为在每次查询时完全加载这些列可能会耗费时间和/或内存。当实体加载时,SQLAlchemy ORM 提供了各种控制列加载的方式。

本节大多数示例演示了ORM 加载器选项。这些是传递给 Select.options() 方法的小构造,该方法是 Select 对象的一部分,当对象编译为 SQL 字符串时,ORM 将使用它们。

使用 load_only() 减少加载的列

load_only()加载器选项是在加载对象时最为便捷的选项,当已知只有少量列将被访问时,可以使用该选项。该选项接受一个可变数量的类绑定属性对象,指示应该加载的列映射属性,除了主键之外的所有其他列映射属性将不包括在检索的列中。在下面的示例中,Book 类包含列 .title.summary.cover_photo。使用 load_only() 我们可以指示 ORM 仅预先加载 .title.summary 列:

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

在上面的示例中,SELECT 语句省略了 .cover_photo 列,并仅包括 .title.summary,以及主键列 .id;ORM 通常会始终获取主键列,因为这些列是必需的,以建立行的标识。

加载后,对象通常将对其余未加载属性应用延迟加载行为,这意味着当首次访问时,将在当前事务中发出一个 SQL 语句以加载值。在下面的示例中,访问 .cover_photo 会发出一个 SELECT 语句来加载其值:

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

惰性加载始终使用对象处于 持久 状态的 Session 进行。如果对象从任何 Session 中 分离,操作将失败,引发异常。

作为在访问时进行惰性加载的替代方法,延迟列还可以配置为在访问时引发信息异常,而不考虑它们的附加状态。当使用 load_only() 构造时,可以使用 load_only.raiseload 参数来指示此行为。有关背景和示例,请参阅 使用 raiseload 防止延迟列加载 部分。

提示

正如其他地方所指出的,当使用异步 I/O(asyncio) 时,惰性加载不可用。

使用 load_only() 处理多个实体

load_only() 限制自己仅适用于其属性列表中引用的单个实体(目前不允许传递跨越多个实体的属性列表)。在下面的示例中,给定的 load_only() 选项仅适用于 Book 实体。选择的 User 实体不受影响;在生成的 SELECT 语句中,所有 user_account 列均存在,而 book 表仅存在 book.idbook.title

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

如果我们想要将 load_only() 选项应用于 UserBook,我们将使用两个单独的选项:

>>> stmt = (
...     select(User, Book)
...     .join_from(User, Book)
...     .options(load_only(User.name), load_only(Book.title))
... )
>>> print(stmt)
SELECT  user_account.id,  user_account.name,  book.id  AS  id_1,  book.title
FROM  user_account  JOIN  book  ON  user_account.id  =  book.owner_id 
在相关对象和集合上使用 load_only()

当使用关系加载器来控制相关对象的加载时,任何关系加载器的 Load.load_only() 方法都可以用于将 load_only() 规则应用于子实体上的列。在下面的示例中,selectinload() 用于在每个 User 对象上加载相关的 books 集合。通过将 Load.load_only() 应用于结果选项对象,当为关系加载对象时,生成的 SELECT 将仅引用 title 列以及主键列:

>>> from sqlalchemy.orm import selectinload
>>> stmt = select(User).options(selectinload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.owner_id  AS  book_owner_id,  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  book.owner_id  IN  (?,  ?)
[...]  (1,  2)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']

load_only() 也可以应用于子实体,而无需声明要为关系本身使用的加载样式。如果我们不想更改 User.books 的默认加载方式,但仍然要应用于 Book 的 load only 规则,我们将使用 defaultload() 选项进行链接,在这种情况下,它将保留默认关系加载样式 "lazy",并将我们的自定义 load_only() 规则应用于为每个 User.books 集合发出的 SELECT 语句:

>>> from sqlalchemy.orm import defaultload
>>> stmt = select(User).options(defaultload(User.books).load_only(Book.title))
>>> for user in session.scalars(stmt):
...     print(f"{user.fullname}   {[b.title for b in user.books]}")
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
[...]  ()
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (1,)
Spongebob Squarepants   ['100 Years of Krabby Patties', 'Sea Catch 22', 'The Sea Grapes of Wrath']
SELECT  book.id  AS  book_id,  book.title  AS  book_title
FROM  book
WHERE  ?  =  book.owner_id
[...]  (2,)
Sandy Cheeks   ['A Nut Like No Other', 'Geodesic Domes: A Retrospective', 'Rocketry for Squirrels']
```### 使用 `defer()` 省略特定列
`defer()` 加载器选项是 `load_only()` 的一种更细粒度的替代方案,它允许将单个特定列标记为“不加载”。在下面的示例中,`defer()` 直接应用于 `.cover_photo` 列,而所有其他列的行为保持不变:
```py
>>> from sqlalchemy.orm import defer
>>> stmt = select(Book).where(Book.owner_id == 2).options(defer(Book.cover_photo))
>>> books = session.scalars(stmt).all()
SELECT  book.id,  book.owner_id,  book.title,  book.summary
FROM  book
WHERE  book.owner_id  =  ?
[...]  (2,)
>>> for book in books:
...     print(f"{book.title}: {book.summary}")
A Nut Like No Other: some long summary
Geodesic Domes: A Retrospective: another long summary
Rocketry for Squirrels: yet another summary

load_only() 一样,未加载的列默认情况下会在使用 惰性加载 访问时加载自身:

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

可以在一条语句中使用多个 defer() 选项来标记多个列为延迟加载。

load_only() 一样,defer() 选项也包括使延迟属性在访问时引发异常而不是惰性加载的能力。这在部分 使用 raiseload 防止延迟列加载 中有所说明。 ### 使用 raiseload 防止延迟列加载

在使用 load_only()defer()  加载器选项时,对于对象上标记为延迟加载的属性,默认行为是在首次访问时,在当前事务中发出 SELECT  语句以加载它们的值。通常需要防止此加载发生,并在访问属性时引发异常,指示没有预期需要为该列查询数据库。典型的场景是使用已知对操作进行操作所需的所有列加载对象,然后将它们传递到视图层。应该捕获在视图层内部发出的任何进一步的  SQL 操作,以便可以调整预先加载的操作以适应该额外的数据,而不是产生额外的惰性加载。

对于此用例,defer()load_only()选项包括一个布尔参数defer.raiseload,当设置为True时,将导致受影响的属性在访问时引发异常。在下面的示例中,延迟加载的列.cover_photo将禁止属性访问:

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

当使用load_only()指定一组非延迟加载列时,可以使用load_only.raiseload参数来应用raiseload行为到其余列,该参数将应用于所有延迟属性:

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

注意

目前尚不可能在一条语句中混合使用指向同一实体的load_only()defer()选项,以改变某些属性的raiseload行为;目前,这样做将产生未定义的属性加载行为。

另请参阅

defer.raiseload功能是与关系可用的相同“raiseload”功能的列级别版本。对于关系的“raiseload”,请参阅本指南的关系加载技术部分中的使用 raiseload 防止不必要的延迟加载。 ## 在映射上配置列延迟加载

对于映射列,默认情况下,defer()的功能可作为映射列的默认行为,这对于不应在每次查询时无条件加载的列可能是合适的。要配置,请使用mapped_column.deferred参数的mapped_column()。下面的示例说明了对Book的映射,该示例将默认列延迟应用于summarycover_photo列:

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

使用上述映射,针对Book的查询将自动不包括summarycover_photo列:

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

与所有延迟加载一样,当首次访问已加载对象上的延迟属性时,默认行为是它们将延迟加载它们的值:

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

defer()load_only()加载器选项一样,映射器级别的延迟加载还包括一个选项,当语句中没有其他选项时,可以发生raiseload行为,而不是惰性加载。这允许映射其中某些列默认情况下不加载,并且在语句中不使用明确指令时也永远不会懒加载。有关如何配置和使用此行为的背景信息,请参阅配置映射器级别的raiseload行为一节。

对于命令式映射器、映射 SQL 表达式使用deferred()

deferred()函数是早期的、更通用的“延迟列”映射指令,在引入mapped_column()构造之前就存在于 SQLAlchemy 中。

在配置 ORM 映射器时使用deferred(),并接受任意 SQL 表达式或Column对象。因此,它适用于非声明式命令式映射,将其传递给map_imperatively.properties字典:

from sqlalchemy import Blob
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Text
from sqlalchemy.orm import registry
mapper_registry = registry()
book_table = Table(
    "book",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("title", String(50)),
    Column("summary", Text),
    Column("cover_image", Blob),
)
class Book:
    pass
mapper_registry.map_imperatively(
    Book,
    book_table,
    properties={
        "summary": deferred(book_table.c.summary),
        "cover_image": deferred(book_table.c.cover_image),
    },
)

当映射的 SQL 表达式应该延迟加载时,deferred()也可以用于替代column_property()

from sqlalchemy.orm import deferred
class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    firstname: Mapped[str] = mapped_column()
    lastname: Mapped[str] = mapped_column()
    fullname: Mapped[str] = deferred(firstname + " " + lastname)

另请参阅

使用 column_property - 在 SQL 表达式作为映射属性一节中

应用负载、持久性和映射选项到命令式表列 - 在使用声明式配置表一节中

使用undefer()来“急切地”加载延迟列

对于默认配置为延迟加载的映射上的列,undefer()选项将导致任何通常延迟加载的列变为未延迟加载,即与映射的所有其他列一起提前加载。例如,我们可以将undefer()应用于在前述映射中标记为延迟加载的Book.summary列:

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

现在,Book.summary列已经被急切地加载,并且可以在不发出额外 SQL 的情况下访问:

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

将延迟列分组加载

通常,当列使用 mapped_column(deferred=True) 进行映射时,当在对象上访问延迟属性时,将发出 SQL 仅加载该特定列而不加载其他列,即使映射还有其他标记为延迟的列。在延迟属性是应一次性加载的一组属性的常见情况下,而不是为每个属性单独发出 SQL,可以使用 mapped_column.deferred_group 参数,该参数接受一个任意字符串,该字符串将定义要取消延迟的一组常见列:

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

使用上述映射,访问 summarycover_photo 将同时加载两个列,只需使用一个 SELECT 语句:

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

使用 undefer_group() 按组取消延迟加载

如果在前一节中引入了 mapped_column.deferred_group 配置了延迟列,则可以指示整个组使用 undefer_group() 选项进行急切加载,传递要急切加载的组的字符串名称:

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

summarycover_photo 都可以在不加载其他内容的情况下使用:

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

使用通配符取消延迟加载

大多数 ORM 加载器选项接受通配符表达式,由 "*" 表示,表示该选项应用于所有相关属性。如果映射具有一系列延迟列,则可以一次性取消所有这些列的延迟,而无需使用组名,只需指定通配符:

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


SqlAlchemy 2.0 中文文档(十八)(2)https://developer.aliyun.com/article/1562921

相关文章
|
5月前
|
SQL 关系型数据库 API
SqlAlchemy 2.0 中文文档(十七)(4)
SqlAlchemy 2.0 中文文档(十七)
94 4
|
5月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(十七)(3)
SqlAlchemy 2.0 中文文档(十七)
43 4
|
5月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(十七)(2)
SqlAlchemy 2.0 中文文档(十七)
46 4
|
5月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(十九)(2)
SqlAlchemy 2.0 中文文档(十九)
44 2
|
5月前
|
SQL 测试技术 Go
SqlAlchemy 2.0 中文文档(十八)(5)
SqlAlchemy 2.0 中文文档(十八)
31 1
|
5月前
|
SQL 测试技术 Go
SqlAlchemy 2.0 中文文档(十八)(3)
SqlAlchemy 2.0 中文文档(十八)
31 1
|
5月前
|
SQL 前端开发 Go
SqlAlchemy 2.0 中文文档(十八)(4)
SqlAlchemy 2.0 中文文档(十八)
32 1
|
5月前
|
SQL 测试技术 Go
SqlAlchemy 2.0 中文文档(十八)(2)
SqlAlchemy 2.0 中文文档(十八)
31 1
|
5月前
|
SQL Java Go
SqlAlchemy 2.0 中文文档(十九)(1)
SqlAlchemy 2.0 中文文档(十九)
46 1
|
5月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(十七)(5)
SqlAlchemy 2.0 中文文档(十七)
35 1