SqlAlchemy 2.0 中文文档(十五)(3)https://developer.aliyun.com/article/1562981
同时选择多个 ORM 实体
select()
函数一次接受任意数量的 ORM 类和/或列表达式,包括可以请求多个 ORM 类的情况。当从多个 ORM 类中进行 SELECT 时,它们在每个结果行中基于其类名命名。在下面的示例中,对 User
和 Address
进行 SELECT 的结果行将以 User
和 Address
为名称进行引用:
>>> stmt = select(User, Address).join(User.addresses).order_by(User.id, Address.id) >>> for row in session.execute(stmt): ... print(f"{row.User.name} {row.Address.email_address}") SELECT user_account.id, user_account.name, user_account.fullname, address.id AS id_1, address.user_id, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id ORDER BY user_account.id, address.id [...] () spongebob spongebob@sqlalchemy.org sandy sandy@sqlalchemy.org sandy squirrel@squirrelpower.org patrick pat999@aol.com squidward stentcl@sqlalchemy.org
如果我们想要为这些实体在行中分配不同的名称,我们将使用 aliased()
构造,并使用 aliased.name
参数将它们别名为具有显式名称的实体:
>>> from sqlalchemy.orm import aliased >>> user_cls = aliased(User, name="user_cls") >>> email_cls = aliased(Address, name="email") >>> stmt = ( ... select(user_cls, email_cls) ... .join(user_cls.addresses.of_type(email_cls)) ... .order_by(user_cls.id, email_cls.id) ... ) >>> row = session.execute(stmt).first() SELECT user_cls.id, user_cls.name, user_cls.fullname, email.id AS id_1, email.user_id, email.email_address FROM user_account AS user_cls JOIN address AS email ON user_cls.id = email.user_id ORDER BY user_cls.id, email.id [...] () >>> print(f"{row.user_cls.name} {row.email.email_address}") spongebob spongebob@sqlalchemy.org
上面的别名形式在使用关系来在别名目标之间进行连接中进一步讨论。
现有的 Select
结构也可以使用 Select.add_columns()
方法将 ORM 类和/或列表达式添加到其列子句中。我们也可以使用这种形式来生成与上面相同的语句:
>>> stmt = ( ... select(User).join(User.addresses).add_columns(Address).order_by(User.id, Address.id) ... ) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname, address.id AS id_1, address.user_id, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id ORDER BY user_account.id, address.id
选择单个属性
映射类上的属性,如 User.name
和 Address.email_address
,当传递给 select()
时,可以像 Column
或其他 SQL 表达式对象一样使用。创建针对特定列的 select()
将返回 Row
对象,而不是像 User
或 Address
对象那样的实体。每个 Row
将分别表示每个列:
>>> result = session.execute( ... select(User.name, Address.email_address) ... .join(User.addresses) ... .order_by(User.id, Address.id) ... ) SELECT user_account.name, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id ORDER BY user_account.id, address.id [...] ()
上面的语句返回具有 name
和 email_address
列的 Row
对象,如下运行时演示所示:
>>> for row in result: ... print(f"{row.name} {row.email_address}") spongebob spongebob@sqlalchemy.org sandy sandy@sqlalchemy.org sandy squirrel@squirrelpower.org patrick pat999@aol.com squidward stentcl@sqlalchemy.org
使用 Bundle 分组选择的属性
Bundle
构造是一个可扩展的仅限 ORM 的构造,允许将列表达式集合分组在结果行中:
>>> from sqlalchemy.orm import Bundle >>> stmt = select( ... Bundle("user", User.name, User.fullname), ... Bundle("email", Address.email_address), ... ).join_from(User, Address) >>> for row in session.execute(stmt): ... print(f"{row.user.name} {row.user.fullname} {row.email.email_address}") SELECT user_account.name, user_account.fullname, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id [...] () spongebob Spongebob Squarepants spongebob@sqlalchemy.org sandy Sandy Cheeks sandy@sqlalchemy.org sandy Sandy Cheeks squirrel@squirrelpower.org patrick Patrick Star pat999@aol.com squidward Squidward Tentacles stentcl@sqlalchemy.org
Bundle
可能对创建轻量级视图和自定义列分组很有用。Bundle
也可以被子类化以返回替代数据结构;请参见 Bundle.create_row_processor()
以获取示例。
另请参阅
Bundle
Bundle.create_row_processor()
选择 ORM 别名
如使用别名教程中所述,创建 ORM 实体的 SQL 别名是通过对映射类使用 aliased()
构造完成的:
>>> from sqlalchemy.orm import aliased >>> u1 = aliased(User) >>> print(select(u1).order_by(u1.id)) SELECT user_account_1.id, user_account_1.name, user_account_1.fullname FROM user_account AS user_account_1 ORDER BY user_account_1.id
就像使用Table.alias()
时一样,SQL 别名是匿名命名的。对于从具有显式名称的行中选择实体的情况,也可以传递aliased.name
参数:
>>> from sqlalchemy.orm import aliased >>> u1 = aliased(User, name="u1") >>> stmt = select(u1).order_by(u1.id) >>> row = session.execute(stmt).first() SELECT u1.id, u1.name, u1.fullname FROM user_account AS u1 ORDER BY u1.id [...] () >>> print(f"{row.u1.name}") spongebob
另请参阅
aliased
结构对于多种用例至关重要,包括:
- 利用 ORM 的子查询;章节从子查询中选择实体和与子查询连接进一步讨论了这一点。
- 控制结果集中实体的名称;参见同时选择多个 ORM 实体以获取示例
- 多次连接到同一 ORM 实体;参见使用关系在别名目标之间连接以获取示例。
从文本语句中获取 ORM 结果
对象关系映射(ORM)支持从其他来源的 SELECT 语句加载实体。典型用例是文本 SELECT 语句,在 SQLAlchemy 中使用text()
结构表示。text()
结构可以附加有关语句将加载的 ORM 映射列的信息;然后可以将其与 ORM 实体本身关联,以便基于此语句加载 ORM 对象。
给定要加载的文本 SQL 语句:
>>> from sqlalchemy import text >>> textual_sql = text("SELECT id, name, fullname FROM user_account ORDER BY id")
我们可以使用TextClause.columns()
方法向语句添加列信息;当调用此方法时,TextClause
对象将转换为TextualSelect
对象,其承担的角色可与Select
构造类似。TextClause.columns()
方法通常传递Column
对象或等效对象,在这种情况下,我们可以直接使用User
类上的 ORM 映射属性:
>>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)
现在,我们有一个经过 ORM 配置的 SQL 构造,可以分别加载“id”、“name”和“fullname”列。要将此 SELECT 语句用作完整User
实体的来源,我们可以使用Select.from_statement()
方法将这些列链接到常规的 ORM 启用的Select
构造中:
>>> orm_sql = select(User).from_statement(textual_sql) >>> for user_obj in session.execute(orm_sql).scalars(): ... print(user_obj) SELECT id, name, fullname FROM user_account ORDER BY id [...] () User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=2, name='sandy', fullname='Sandy Cheeks') User(id=3, name='patrick', fullname='Patrick Star') User(id=4, name='squidward', fullname='Squidward Tentacles') User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')
相同的TextualSelect
对象也可以使用TextualSelect.subquery()
方法转换为子查询,并使用aliased()
构造将其链接到User
实体,方式与下面讨论的从子查询中选择实体类似:
>>> orm_subquery = aliased(User, textual_sql.subquery()) >>> stmt = select(orm_subquery) >>> for user_obj in session.execute(stmt).scalars(): ... print(user_obj) SELECT anon_1.id, anon_1.name, anon_1.fullname FROM (SELECT id, name, fullname FROM user_account ORDER BY id) AS anon_1 [...] () User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=2, name='sandy', fullname='Sandy Cheeks') User(id=3, name='patrick', fullname='Patrick Star') User(id=4, name='squidward', fullname='Squidward Tentacles') User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')
直接使用TextualSelect
与Select.from_statement()
相比,使用aliased()
的区别在于,在前一种情况下,生成的 SQL 中不会产生子查询。在某些情况下,这可能有利于性能或复杂性方面。
从子查询中选择实体
在前一节讨论的aliased()
构造中,可以与任何Subuqery
构造一起使用,该构造来自诸如Select.subquery()
之类的方法,以将 ORM 实体链接到该子查询返回的列;子查询返回的列与实体映射的列之间必须存在列对应关系,这意味着子查询最终需要源自这些实体,例如下面的示例:
>>> inner_stmt = select(User).where(User.id < 7).order_by(User.id) >>> subq = inner_stmt.subquery() >>> aliased_user = aliased(User, subq) >>> stmt = select(aliased_user) >>> for user_obj in session.execute(stmt).scalars(): ... print(user_obj) SELECT anon_1.id, anon_1.name, anon_1.fullname FROM (SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname FROM user_account WHERE user_account.id < ? ORDER BY user_account.id) AS anon_1 [generated in ...] (7,) User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=2, name='sandy', fullname='Sandy Cheeks') User(id=3, name='patrick', fullname='Patrick Star') User(id=4, name='squidward', fullname='Squidward Tentacles') User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')
另请参见
ORM 实体子查询/CTEs - 在 SQLAlchemy 统一教程中
加入子查询
从 UNION 和其他集合操作中选择实体
union()
和 union_all()
函数是最常见的集合操作,与其他集合操作(如 except_()
、intersect()
等)一起,提供了一个称为 CompoundSelect
的对象,它由多个通过集合操作关键字连接的 Select
构造组成。ORM 实体可以通过简单的复合选择使用 Select.from_statement()
方法进行选择,该方法在 从文本语句中获取 ORM 结果 中已经说明。在这种方法中,UNION 语句是将被渲染的完整语句,不能在使用 Select.from_statement()
后添加额外的条件:
>>> from sqlalchemy import union_all >>> u = union_all( ... select(User).where(User.id < 2), select(User).where(User.id == 3) ... ).order_by(User.id) >>> stmt = select(User).from_statement(u) >>> for user_obj in session.execute(stmt).scalars(): ... print(user_obj) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.id < ? UNION ALL SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.id = ? ORDER BY id [generated in ...] (2, 3) User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=3, name='patrick', fullname='Patrick Star')
CompoundSelect
构造可以在更灵活的查询中更灵活地使用,该查询可以通过将其组织成子查询并使用 aliased()
将其链接到 ORM 实体来进一步修改,如 从子查询中选择实体 中已说明。在下面的示例中,我们首先使用 CompoundSelect.subquery()
创建 UNION ALL 语句的子查询,然后将其打包到 aliased()
构造中,在这里它可以像其他映射实体一样用于 select()
构造中,包括我们可以根据其导出的列添加过滤和排序条件:
>>> subq = union_all( ... select(User).where(User.id < 2), select(User).where(User.id == 3) ... ).subquery() >>> user_alias = aliased(User, subq) >>> stmt = select(user_alias).order_by(user_alias.id) >>> for user_obj in session.execute(stmt).scalars(): ... print(user_obj) SELECT anon_1.id, anon_1.name, anon_1.fullname FROM (SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname FROM user_account WHERE user_account.id < ? UNION ALL SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname FROM user_account WHERE user_account.id = ?) AS anon_1 ORDER BY anon_1.id [generated in ...] (2, 3) User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=3, name='patrick', fullname='Patrick Star')
另请参阅
从 Union 中选择 ORM 实体 - 在 SQLAlchemy 统一教程 中
连接
Select.join()
和Select.join_from()
方法用于构建针对 SELECT 语句的 SQL JOINs。
本节将详细介绍这些方法在 ORM 中的用例。有关从 Core 视角的使用的一般概述,请参阅显式 FROM 子句和 JOINs 中的 SQLAlchemy 统一教程。
在 ORM 上下文中使用Select.join()
进行 2.0 风格查询的用法基本上等同于除了遗留用例之外,在 1.x 风格查询中使用Query.join()
方法的用法。
简单的关系连接
考虑两个类User
和Address
之间的映射,其中关系User.addresses
表示与每个User
关联的Address
对象的集合。Select.join()
最常见的用法是沿着这种关系创建一个 JOIN,使用User.addresses
属性作为指示器来指示应该如何发生这种情况:
>>> stmt = select(User).join(User.addresses)
在上面的例子中,对Select.join()
和User.addresses
的调用将导致大致等效的 SQL 语句:
>>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN address ON user_account.id = address.user_id
在上面的例子中,我们将User.addresses
称为传递给Select.join()
的“on clause”,即它指示如何构建 JOIN 的“ON”部分。
提示
请注意,使用Select.join()
从一个实体连接到另一个实体会影响 SELECT 语句的 FROM 子句,但不会影响列子句;在这个示例中,SELECT 语句将继续仅返回User
实体的行。要同时从User
和Address
中选择列/实体,必须在select()
函数中也命名Address
实体,或者在使用Select.add_columns()
方法后将其添加到Select
构造中。有关这两种形式的示例,请参阅同时选择多个 ORM 实体部分。
链式多重连接
要构建连接链,可以使用多个Select.join()
调用。关联属性同时涵盖连接的左侧和右侧。考虑额外的实体Order
和Item
,其中User.orders
关系指向Order
实体,而Order.items
关系指向Item
实体,通过一个关联表order_items
。两个Select.join()
调用将导致第一个 JOIN 从User
到Order
,第二个从Order
到Item
。然而,由于Order.items
是多对多关系,它会导致两个独立的 JOIN 元素,总共有三个 JOIN 元素在结果 SQL 中:
>>> stmt = select(User).join(User.orders).join(Order.items) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN user_order ON user_account.id = user_order.user_id JOIN order_items AS order_items_1 ON user_order.id = order_items_1.order_id JOIN item ON item.id = order_items_1.item_id
每次调用Select.join()
方法的顺序只有在我们想要从中连接的“左”侧需要出现在 FROM 列表中时才重要,然后我们才能指示一个新的目标。例如,如果我们指定select(User).join(Order.items).join(User.orders)
,Select.join()
就不会知道如何正确地进行连接,它会引发错误。在正确的实践中,应该以与我们希望在 SQL 中呈现 JOIN 子句相匹配的方式调用Select.join()
方法,并且每次调用都应该表示从前面的内容清晰链接。
我们在 FROM 子句中定位的所有元素仍然可用作继续连接 FROM 的潜在点。例如,我们可以继续将其他元素添加到上述User
实体的 FROM 连接中,例如在我们的连接链中添加User.addresses
关系:
>>> stmt = select(User).join(User.orders).join(Order.items).join(User.addresses) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN user_order ON user_account.id = user_order.user_id JOIN order_items AS order_items_1 ON user_order.id = order_items_1.order_id JOIN item ON item.id = order_items_1.item_id JOIN address ON user_account.id = address.user_id
连接到目标实体
第二种形式的Select.join()
允许将任何映射实体或核心可选择的构造作为目标。在此用法中,Select.join()
将尝试推断JOIN 的 ON 子句,使用两个实体之间的自然外键关系:
>>> stmt = select(User).join(Address) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN address ON user_account.id = address.user_id
在上述调用形式中,Select.join()
被调用以自动推断“on clause”。如果两个映射的Table
构造之间没有设置任何ForeignKeyConstraint
,或者如果它们之间有多个ForeignKeyConstraint
链接,使得要使用的适当约束不明确,此调用形式最终将引发错误。
注意
在使用Select.join()
或Select.join_from()
而不指定 ON 子句时,ORM 配置的relationship()
构造不会被考虑。仅在尝试为 JOIN 推断 ON 子句时,才会在映射的Table
对象级别上查阅配置的ForeignKeyConstraint
关系。
到具有 ON 子句的目标的连接
第三种调用形式允许显式传递目标实体以及 ON 子句。包含 SQL 表达式作为 ON 子句的示例如下:
>>> stmt = select(User).join(Address, User.id == Address.user_id) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN address ON user_account.id = address.user_id
基于表达式的 ON 子句也可以是一个relationship()
绑定属性,就像在简单关系连接中使用的方式一样:
>>> stmt = select(User).join(Address, User.addresses) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN address ON user_account.id = address.user_id
上述示例似乎多余,因为它以两种不同的方式指示了Address
的目标;然而,当加入到别名实体时,这种形式的实用性变得明显;请参见使用关系连接别名目标中的示例。### 将关系与自定义 ON 条件结合使用
由relationship()
构造生成的 ON 子句可以通过附加的额外条件进行增强。这对于快速限制特定关系路径上连接范围的方式以及配置加载策略(如joinedload()
和selectinload()
)非常有用。PropComparator.and_()
方法按位置接受一系列 SQL 表达式,这些表达式将通过 AND 连接到 JOIN 的 ON 子句。例如,如果我们想要从User
JOIN 到Address
,但也限制 ON 条件仅适用于某些电子邮件地址:
>>> stmt = select(User.fullname).join( ... User.addresses.and_(Address.email_address == "squirrel@squirrelpower.org") ... ) >>> session.execute(stmt).all() SELECT user_account.fullname FROM user_account JOIN address ON user_account.id = address.user_id AND address.email_address = ? [...] ('squirrel@squirrelpower.org',) [('Sandy Cheeks',)]
另请参阅
PropComparator.and_()
方法也适用于加载策略,如joinedload()
和selectinload()
。请参见向加载选项添加条件部分。### 使用关系连接别名目标
当使用relationship()
绑定属性构建连接以指示 ON 子句时,使用带有 ON 子句的目标的连接中说明的两参数语法可以扩展为与aliased()
构造一起使用,以指示 SQL 别名作为连接的目标,同时仍然利用relationship()
绑定属性来指示 ON 子句,如下例所示,其中User
实体两次与两个不同的aliased()
构造连接到Address
实体:
>>> address_alias_1 = aliased(Address) >>> address_alias_2 = aliased(Address) >>> stmt = ( ... select(User) ... .join(address_alias_1, User.addresses) ... .where(address_alias_1.email_address == "patrick@aol.com") ... .join(address_alias_2, User.addresses) ... .where(address_alias_2.email_address == "patrick@gmail.com") ... ) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN address AS address_1 ON user_account.id = address_1.user_id JOIN address AS address_2 ON user_account.id = address_2.user_id WHERE address_1.email_address = :email_address_1 AND address_2.email_address = :email_address_2
同样的模式可以更简洁地使用修饰符 PropComparator.of_type()
表达,该修饰符可以应用于 relationship()
绑定的属性,传递目标实体以一步指示目标。下面的示例使用 PropComparator.of_type()
来生成与刚刚示例相同的 SQL 语句:
>>> print( ... select(User) ... .join(User.addresses.of_type(address_alias_1)) ... .where(address_alias_1.email_address == "patrick@aol.com") ... .join(User.addresses.of_type(address_alias_2)) ... .where(address_alias_2.email_address == "patrick@gmail.com") ... ) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN address AS address_1 ON user_account.id = address_1.user_id JOIN address AS address_2 ON user_account.id = address_2.user_id WHERE address_1.email_address = :email_address_1 AND address_2.email_address = :email_address_2
要利用一个 relationship()
来构建一个从别名实体连接的连接,该属性直接从 aliased()
构造中可用:
>>> user_alias_1 = aliased(User) >>> print(select(user_alias_1.name).join(user_alias_1.addresses)) SELECT user_account_1.name FROM user_account AS user_account_1 JOIN address ON user_account_1.id = address.user_id ```### 连接到子查询 连接的目标可以是任何可选择的实体,包括子查询。在使用 ORM 时,通常会以 `aliased()` 构造来表示这些目标,但这并不是严格要求的,特别是如果连接的实体不会在结果中返回时。例如,要从 `User` 实体连接到 `Address` 实体,在这里 `Address` 实体被表示为一行限制的子查询,我们首先使用 `Select.subquery()` 构造一个 `Subquery` 对象,然后可以将其用作 `Select.join()` 方法的目标: ```py >>> subq = select(Address).where(Address.email_address == "pat999@aol.com").subquery() >>> stmt = select(User).join(subq, User.id == subq.c.user_id) >>> print(stmt) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account JOIN (SELECT address.id AS id, address.user_id AS user_id, address.email_address AS email_address FROM address WHERE address.email_address = :email_address_1) AS anon_1 ON user_account.id = anon_1.user_id
上述的 SELECT 语句在通过 Session.execute()
调用时将返回包含 User
实体的行,但不包含 Address
实体。为了将 Address
实体包含到将在结果集中返回的实体集合中,我们构造了一个针对 Address
实体和 Subquery
对象的 aliased()
对象。我们还可以希望对 aliased()
构造应用一个名称,例如下面使用的 "address"
,这样我们就可以在结果行中通过名称引用它:
>>> address_subq = aliased(Address, subq, name="address") >>> stmt = select(User, address_subq).join(address_subq) >>> for row in session.execute(stmt): ... print(f"{row.User} {row.address}") SELECT user_account.id, user_account.name, user_account.fullname, anon_1.id AS id_1, anon_1.user_id, anon_1.email_address FROM user_account JOIN (SELECT address.id AS id, address.user_id AS user_id, address.email_address AS email_address FROM address WHERE address.email_address = ?) AS anon_1 ON user_account.id = anon_1.user_id [...] ('pat999@aol.com',) User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
SqlAlchemy 2.0 中文文档(十五)(5)https://developer.aliyun.com/article/1562983