SQLAlchemy 2.0 文档
SQL 表达式作为映射属性¶
映射类上的属性可以链接到 SQL 表达式,这些表达式可以在查询中使用。
使用混合属性¶
将相对简单的 SQL 表达式链接到类的最简单和最灵活的方法是使用所谓的“混合属性”,在 混合属性 章节中进行了描述。 混合属性提供了在 Python 级别和 SQL 表达式级别都有效的表达式。 例如,下面我们映射一个 User
类,其中包含属性 firstname
和 lastname
,并包含一个混合属性,它将为我们提供 fullname
,它是两个属性的字符串连接
from sqlalchemy.ext.hybrid import hybrid_property
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
firstname = mapped_column(String(50))
lastname = mapped_column(String(50))
@hybrid_property
def fullname(self):
return self.firstname + " " + self.lastname
在上面,fullname
属性在实例级别和类级别都被解释,因此它可以从实例中获得
some_user = session.scalars(select(User).limit(1)).first()
print(some_user.fullname)
并且可以在查询中使用
some_user = session.scalars(
select(User).where(User.fullname == "John Smith").limit(1)
).first()
字符串连接示例是一个简单的示例,其中 Python 表达式可以在实例级别和类级别双重使用。 通常,SQL 表达式必须与 Python 表达式区分开来,这可以使用 hybrid_property.expression()
来实现。 下面我们说明需要在混合属性中存在条件的情况,使用 Python 中的 if
语句和 SQL 表达式的 case()
构造
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import case
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
firstname = mapped_column(String(50))
lastname = mapped_column(String(50))
@hybrid_property
def fullname(self):
if self.firstname is not None:
return self.firstname + " " + self.lastname
else:
return self.lastname
@fullname.expression
def fullname(cls):
return case(
(cls.firstname != None, cls.firstname + " " + cls.lastname),
else_=cls.lastname,
)
使用 column_property¶
column_property()
函数可用于以类似于常规映射的 Column
的方式映射 SQL 表达式。 使用此技术,属性在加载时与其他所有列映射属性一起加载。 在某些情况下,这比使用混合属性更具优势,因为值可以与对象的父行同时预先加载,特别是当表达式链接到其他表(通常作为关联子查询)以访问通常在已加载对象上不可用的数据时。
使用 column_property()
进行 SQL 表达式的缺点包括表达式必须与为类整体发出的 SELECT 语句兼容,并且在使用来自声明式 mixin 的 column_property()
时,也可能发生一些配置怪癖。
我们的“fullname”示例可以使用 column_property()
表示如下
from sqlalchemy.orm import column_property
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
firstname = mapped_column(String(50))
lastname = mapped_column(String(50))
fullname = column_property(firstname + " " + lastname)
也可以使用关联子查询。 下面我们使用 select()
构造来创建一个 ScalarSelect
,表示面向列的 SELECT 语句,它将特定 User
可用的 Address
对象的计数链接在一起
from sqlalchemy.orm import column_property
from sqlalchemy import select, func
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class Address(Base):
__tablename__ = "address"
id = mapped_column(Integer, primary_key=True)
user_id = mapped_column(Integer, ForeignKey("user.id"))
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
address_count = column_property(
select(func.count(Address.id))
.where(Address.user_id == id)
.correlate_except(Address)
.scalar_subquery()
)
在上面的示例中,我们定义了一个 ScalarSelect()
构造,如下所示
stmt = (
select(func.count(Address.id))
.where(Address.user_id == id)
.correlate_except(Address)
.scalar_subquery()
)
上面,我们首先使用 select()
创建一个 Select
构造,然后我们使用 Select.scalar_subquery()
方法将其转换为 标量子查询,表明我们打算在列表达式上下文中使用此 Select
语句。
在 Select
本身中,我们选择 Address.id
行的计数,其中 Address.user_id
列等于 id
,这在 User
类的上下文中是名为 id
的 Column
(请注意,id
也是 Python 内置函数的名称,这不是我们想要在此处使用的 - 如果我们在 User
类定义之外,我们将使用 User.id
)。
Select.correlate_except()
方法指示此 select()
的 FROM 子句中的每个元素都可以从 FROM 列表中省略(即,与针对 User
的封闭 SELECT 语句关联),除了与 Address
对应的元素。 这不是绝对必要的,但在 User
和 Address
表之间进行长字符串连接的情况下,可以防止 Address
被意外地从 FROM 列表中省略,在这些情况下,针对 Address
的 SELECT 语句是嵌套的。
对于引用来自多对多关系的列的 column_property()
,使用 and_()
将关联表的字段连接到关系中的两个表
from sqlalchemy import and_
class Author(Base):
# ...
book_count = column_property(
select(func.count(books.c.id))
.where(
and_(
book_authors.c.author_id == authors.c.id,
book_authors.c.book_id == books.c.id,
)
)
.scalar_subquery()
)
向现有声明式映射类添加 column_property()¶
如果导入问题阻止 column_property()
与类内联定义,则可以在配置两者后将其分配给类。 当使用利用声明式基类的映射(即由 DeclarativeBase
超类或旧函数(如 declarative_base()
)生成的映射)时,此属性分配具有调用 Mapper.add_property()
以在事后添加附加属性的效果
# only works if a declarative base class is in use
User.address_count = column_property(
select(func.count(Address.id)).where(Address.user_id == User.id).scalar_subquery()
)
当使用不使用声明式基类的映射样式(例如 registry.mapped()
装饰器)时,可以在底层 Mapper
对象上显式调用 Mapper.add_property()
方法,可以使用 inspect()
获取该对象
from sqlalchemy.orm import registry
reg = registry()
@reg.mapped
class User:
__tablename__ = "user"
# ... additional mapping directives
# later ...
# works for any kind of mapping
from sqlalchemy import inspect
inspect(User).add_property(
column_property(
select(func.count(Address.id))
.where(Address.user_id == User.id)
.scalar_subquery()
)
)
另请参阅
在映射时从 Column Properties 组合¶
可以创建将多个 ColumnProperty
对象组合在一起的映射。 当 ColumnProperty
在 Core 表达式上下文中使用时,它将被解释为 SQL 表达式,前提是它以现有的表达式对象为目标; 这通过 Core 检测到该对象具有返回 SQL 表达式的 __clause_element__()
方法来实现。 但是,如果 ColumnProperty
用作表达式中的前导对象,而没有其他 Core SQL 表达式对象以其为目标,则 ColumnProperty.expression
属性将返回底层 SQL 表达式,以便可以一致地使用它来构建 SQL 表达式。 下面,File
类包含一个属性 File.path
,它将字符串标记连接到 File.filename
属性,而 File.filename
属性本身就是一个 ColumnProperty
class File(Base):
__tablename__ = "file"
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String(64))
extension = mapped_column(String(8))
filename = column_property(name + "." + extension)
path = column_property("C:/" + filename.expression)
当 File
类在表达式中正常使用时,分配给 filename
和 path
的属性可以直接使用。 只有在映射定义中直接使用 ColumnProperty
时,才需要使用 ColumnProperty.expression
属性
stmt = select(File.path).where(File.filename == "foo.txt")
将 Column Deferral 与 column_property()
一起使用¶
在 ORM 查询指南 的 限制使用列延迟加载加载哪些列 中介绍的列延迟加载功能可以在映射时应用于由 column_property()
映射的 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)
使用普通描述符¶
在 SQL 查询比 column_property()
或 hybrid_property
可以提供的更复杂的情况下,可以使用作为属性访问的常规 Python 函数,前提是表达式只需要在已加载的实例上可用。 该函数使用 Python 自己的 @property
装饰器进行装饰,以将其标记为只读属性。 在函数内部,使用 object_session()
来定位与当前对象对应的 Session
,然后使用该会话发出查询
from sqlalchemy.orm import object_session
from sqlalchemy import select, func
class User(Base):
__tablename__ = "user"
id = mapped_column(Integer, primary_key=True)
firstname = mapped_column(String(50))
lastname = mapped_column(String(50))
@property
def address_count(self):
return object_session(self).scalar(
select(func.count(Address.id)).where(Address.user_id == self.id)
)
普通描述符方法作为最后的手段很有用,但在通常情况下,它的性能不如混合属性和列属性方法,因为它需要在每次访问时发出 SQL 查询。
查询时 SQL 表达式作为映射属性¶
除了能够在映射类上配置固定的 SQL 表达式外,SQLAlchemy ORM 还包括一项功能,其中对象可以使用在查询时设置为其状态一部分的任意 SQL 表达式的结果进行加载。 通过使用 query_expression()
配置 ORM 映射属性,然后在查询时使用 with_expression()
加载器选项,可以使用此行为。 有关示例映射和用法,请参阅 将任意 SQL 表达式加载到对象上 章节。
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。 文档上次生成时间:2025 年 3 月 11 日星期二下午 02:40:17 EDT