使用 SELECT 语句

对于 Core 和 ORM,select() 函数生成一个 Select 构建,用于所有 SELECT 查询。传递给像 Core 中的 Connection.execute() 和 ORM 中的 Session.execute() 这样的方法,一个 SELECT 语句将在当前事务中发出,并且结果行可以通过返回的 Result 对象获取。

ORM 阅读器 - 这里的内容同样适用于 Core 和 ORM 使用,并且这里提到了基本的 ORM 变体用例。但是还有许多 ORM 特定的功能可用,这些功能在 ORM 查询指南 中有记录。

select() SQL 表达式构建

select() 构建以与 insert() 相同的方式构建语句,使用一种 生成式 方法,其中每个方法都在对象上构建更多状态。与其他 SQL 构建一样,它可以在适当的位置被字符串化

>>> from sqlalchemy import select
>>> stmt = select(user_table).where(user_table.c.name == "spongebob")
>>> print(stmt)
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = :name_1

同样,与所有其他语句级 SQL 构建一样,要实际运行语句,我们将它传递给执行方法。由于 SELECT 语句返回行,因此我们始终可以迭代结果对象以获取 Row 对象

>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(row)
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? [...] ('spongebob',)
(1, 'spongebob', 'Spongebob Squarepants')
ROLLBACK

使用 ORM 时,特别是对于针对 ORM 实体构建的 select() 构建,我们希望使用 Session.execute() 方法在 Session 上执行它;使用这种方法,我们继续从结果中获得 Row 对象,但是这些行现在能够包含完整的实体,例如 User 类的实例,作为每行中的单个元素

>>> stmt = select(User).where(User.name == "spongebob")
>>> with Session(engine) as session:
...     for row in session.execute(stmt):
...         print(row)
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? [...] ('spongebob',)
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
ROLLBACK

以下部分将更详细地讨论 SELECT 构建。

设置 COLUMNS 和 FROM 子句

select() 函数接受代表任意数量的 Column 和/或 Table 表达式的 positional 元素,以及各种兼容对象,这些对象将被解析为将被 SELECT 的 SQL 表达式列表,这些表达式将在结果集中作为列返回。这些元素在更简单的情况下也用于创建 FROM 子句,该子句是从传递的列和类似表表达式推断出来的

>>> print(select(user_table))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account

要使用 Core 方法从单个列中 SELECT,Column 对象可从 Table.c 访问器中获取,并且可以直接发送;FROM 子句将被推断为所有 Table 和其他 FromClause 对象,这些对象由这些列表示

>>> print(select(user_table.c.name, user_table.c.fullname))
SELECT user_account.name, user_account.fullname FROM user_account

或者,使用任何 FromClause (例如 Table)的 FromClause.c 集合时,可以使用字符串名称的元组为 select() 指定多个列

>>> print(select(user_table.c["name", "fullname"]))
SELECT user_account.name, user_account.fullname FROM user_account

版本 2.0 中新增: 将元组访问器功能添加到 FromClause.c 集合

选择 ORM 实体和列

ORM 实体,例如我们的 User 类以及其上的列映射属性,例如 User.name,也参与 SQL 表达式语言系统,表示表和列。下面说明了一个从 User 实体中 SELECT 的示例,它最终与直接使用 user_table 的方式相同

>>> print(select(User))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account

当使用 ORM 的 Session.execute() 方法执行类似上面的语句时,从完整的实体(如 User)中进行选择与从 user_table 中进行选择存在一个重要的区别,那就是实体本身作为每一行中的单个元素返回。也就是说,当我们从上面的语句中获取行时,由于在要获取的事物列表中只有 User 实体,我们得到的是只有单个元素的 Row 对象,其中包含 User 类的实例。

>>> row = session.execute(select(User)).first()
BEGIN... SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] ()
>>> row (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)

上面的 Row 只有一个元素,代表 User 实体。

>>> row[0]
User(id=1, name='spongebob', fullname='Spongebob Squarepants')

一个非常推荐的便捷方法是使用 Session.scalars() 方法直接执行语句,以实现与上面相同的结果;此方法将返回一个 ScalarResult 对象,该对象一次性提供每一行的第一“列”,在本例中,是 User 类的实例。

>>> user = session.scalars(select(User)).first()
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account [...] ()
>>> user User(id=1, name='spongebob', fullname='Spongebob Squarepants')

或者,我们可以通过使用类绑定的属性,将 ORM 实体的单个列作为结果行中的不同元素进行选择;当这些属性传递给诸如 select() 之类的构造时,它们将解析为由每个属性表示的 Column 或其他 SQL 表达式。

>>> print(select(User.name, User.fullname))
SELECT user_account.name, user_account.fullname FROM user_account

当我们使用 Session.execute() 调用语句时,我们现在会收到每行具有单个元素的行,每个元素对应一个单独的列或其他 SQL 表达式。

>>> row = session.execute(select(User.name, User.fullname)).first()
SELECT user_account.name, user_account.fullname FROM user_account [...] ()
>>> row ('spongebob', 'Spongebob Squarepants')

这些方法也可以混合使用,如下所示,我们将 User 实体的 name 属性作为行的第一个元素进行 SELECT,并将其与第二个元素中的完整 Address 实体组合在一起。

>>> session.execute(
...     select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
... ).all()
SELECT user_account.name, address.id, address.email_address, address.user_id FROM user_account, address WHERE user_account.id = address.user_id ORDER BY address.id [...] ()
[('spongebob', Address(id=1, email_address='[email protected]')), ('sandy', Address(id=2, email_address='[email protected]')), ('sandy', Address(id=3, email_address='[email protected]'))]

有关选择 ORM 实体和列以及将行转换为常用方法的更多讨论,请参阅 选择 ORM 实体和属性

从带标签的 SQL 表达式中选择

ColumnElement.label() 方法以及在 ORM 属性上提供的同名方法提供了列或表达式的 SQL 标签,使其在结果集中具有特定名称。当按名称引用结果行中的任意 SQL 表达式时,这很有帮助。

>>> from sqlalchemy import func, cast
>>> stmt = select(
...     ("Username: " + user_table.c.name).label("username"),
... ).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.username}")
BEGIN (implicit) SELECT ? || user_account.name AS username FROM user_account ORDER BY user_account.name [...] ('Username: ',)
Username: patrick Username: sandy Username: spongebob
ROLLBACK

另请参阅

按标签排序或分组 - 我们创建的标签名称也可以在 Select 的 ORDER BY 或 GROUP BY 子句中引用。

使用文本列表达式进行选择

当我们使用 select() 函数构建 Select 对象时,通常会向其传递一系列使用 表元数据 定义的 TableColumn 对象,或者在使用 ORM 时,我们可能会发送表示表列的 ORM 映射属性。但是,有时还需要在语句中制造任意 SQL 块,例如常量字符串表达式,或者只是某些任意 SQL,这些 SQL 写起来更快。

使用事务和 DBAPI 中介绍的 text() 构造实际上可以直接嵌入到 Select 构造中,如下所示,我们制造一个硬编码的字符串文字 'some phrase' 并将其嵌入到 SELECT 语句中。

>>> from sqlalchemy import text
>>> stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
>>> with engine.connect() as conn:
...     print(conn.execute(stmt).all())
BEGIN (implicit) SELECT 'some phrase', user_account.name FROM user_account ORDER BY user_account.name [generated in ...] ()
[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]
ROLLBACK

虽然 text() 构造可以在大多数地方用于注入文字 SQL 片段,但大多数情况下,我们实际上处理的是每个代表单个“列”表达式的文本单元。在这种常见情况下,我们可以使用 literal_column() 构造来获得更多功能。此对象类似于 text(),不同之处在于它不代表任何形式的任意 SQL,而是明确地代表一个“列”,然后可以对其进行标记并在子查询和其他表达式中引用。

>>> from sqlalchemy import literal_column
>>> stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
...     user_table.c.name
... )
>>> with engine.connect() as conn:
...     for row in conn.execute(stmt):
...         print(f"{row.p}, {row.name}")
BEGIN (implicit) SELECT 'some phrase' AS p, user_account.name FROM user_account ORDER BY user_account.name [generated in ...] ()
some phrase, patrick some phrase, sandy some phrase, spongebob
ROLLBACK

请注意,在这两种情况下,当使用 text()literal_column() 时,我们正在编写语法 SQL 表达式,而不是文字值。因此,我们必须包含要呈现的 SQL 所需的任何引号或语法。

WHERE 子句

SQLAlchemy 允许我们通过将标准 Python 运算符与 Column 和类似对象结合使用来组合 SQL 表达式,例如 name = 'squidward'user_id > 10。对于布尔表达式,大多数 Python 运算符(如 ==!=<>= 等)会生成新的 SQL 表达式对象,而不是普通的布尔 True/False 值。

>>> print(user_table.c.name == "squidward")
user_account.name = :name_1

>>> print(address_table.c.user_id > 10)
address.user_id > :user_id_1

我们可以使用这些表达式通过将结果对象传递给 Select.where() 方法来生成 WHERE 子句。

>>> print(select(user_table).where(user_table.c.name == "squidward"))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = :name_1

要生成由 AND 连接的多个表达式,可以多次调用 Select.where() 方法。

>>> print(
...     select(address_table.c.email_address)
...     .where(user_table.c.name == "squidward")
...     .where(address_table.c.user_id == user_table.c.id)
... )
SELECT address.email_address FROM address, user_account WHERE user_account.name = :name_1 AND address.user_id = user_account.id

一次调用 Select.where() 也接受具有相同效果的多个表达式。

>>> print(
...     select(address_table.c.email_address).where(
...         user_table.c.name == "squidward",
...         address_table.c.user_id == user_table.c.id,
...     )
... )
SELECT address.email_address FROM address, user_account WHERE user_account.name = :name_1 AND address.user_id = user_account.id

“AND”和“OR”连接都可以直接使用 and_()or_() 函数,如下所示,在 ORM 实体方面进行说明。

>>> from sqlalchemy import and_, or_
>>> print(
...     select(Address.email_address).where(
...         and_(
...             or_(User.name == "squidward", User.name == "sandy"),
...             Address.user_id == User.id,
...         )
...     )
... )
SELECT address.email_address FROM address, user_account WHERE (user_account.name = :name_1 OR user_account.name = :name_2) AND address.user_id = user_account.id

对于针对单个实体的简单“相等”比较,还有一个流行的方法,称为 Select.filter_by(),它接受与列键或 ORM 属性名称匹配的关键字参数。它将针对最左边的 FROM 子句或最后加入的实体进行过滤。

>>> print(select(User).filter_by(name="spongebob", fullname="Spongebob Squarepants"))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = :name_1 AND user_account.fullname = :fullname_1

另请参阅

运算符参考 - 对 SQLAlchemy 中大多数 SQL 运算符函数的描述。

显式 FROM 子句和 JOIN

如前所述,FROM 子句通常根据我们在 columns 子句中设置的表达式以及Select 的其他元素进行推断

如果我们在 COLUMNS 子句中设置来自特定Table 的单个列,它也会将该Table 放入 FROM 子句中。

>>> print(select(user_table.c.name))
SELECT user_account.name FROM user_account

如果我们要从两个表中添加列,那么我们会得到一个用逗号分隔的 FROM 子句。

>>> print(select(user_table.c.name, address_table.c.email_address))
SELECT user_account.name, address.email_address FROM user_account, address

为了将这两个表连接在一起,我们通常在Select 上使用两种方法之一。第一种是Select.join_from() 方法,它允许我们明确地指示 JOIN 的左右侧。

>>> print(
...     select(user_table.c.name, address_table.c.email_address).join_from(
...         user_table, address_table
...     )
... )
SELECT user_account.name, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id

另一种是Select.join() 方法,它只指示 JOIN 的右侧,左侧是推断出来的。

>>> print(select(user_table.c.name, address_table.c.email_address).join(address_table))
SELECT user_account.name, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id

我们还可以选择显式地向 FROM 子句添加元素,如果它没有以我们想要的方式从 columns 子句中推断出来。我们使用Select.select_from() 方法来实现这一点,如下所示,我们将user_table 作为 FROM 子句中的第一个元素,并使用Select.join()address_table 设置为第二个元素。

>>> print(select(address_table.c.email_address).select_from(user_table).join(address_table))
SELECT address.email_address FROM user_account JOIN address ON user_account.id = address.user_id

另一个我们可能想要使用Select.select_from() 的例子是,如果我们的 columns 子句没有提供 FROM 子句所需的信息。例如,要从常见的 SQL 表达式count(*) 中 SELECT,我们使用一个名为sqlalchemy.sql.expression.func 的 SQLAlchemy 元素来生成 SQL count() 函数。

>>> from sqlalchemy import func
>>> print(select(func.count("*")).select_from(user_table))
SELECT count(:count_2) AS count_1 FROM user_account

另请参阅

设置连接中最左侧的 FROM 子句 - 在ORM 查询指南 中 - 包含有关Select.select_from()Select.join() 的交互的附加示例和说明。

设置 ON 子句

前面的 JOIN 示例说明了Select 结构可以在两个表之间连接并自动生成 ON 子句。这在这些示例中发生是因为user_tableaddress_table Table 对象包含一个单一的ForeignKeyConstraint 定义,用于形成此 ON 子句。

如果连接的左右目标没有这样的约束,或者存在多个约束,我们需要直接指定 ON 子句。Select.join()Select.join_from() 接受 ON 子句的附加参数,该参数使用与我们在WHERE 子句 中看到的相同的 SQL 表达式机制进行声明。

>>> print(
...     select(address_table.c.email_address)
...     .select_from(user_table)
...     .join(address_table, user_table.c.id == address_table.c.user_id)
... )
SELECT address.email_address FROM user_account JOIN address ON user_account.id = address.user_id

ORM 提示 - 当使用利用relationship() 结构的 ORM 实体时,还有另一种方法可以生成 ON 子句,例如上一节声明映射类 中设置的映射。这是一个独立的主题,将在使用关系连接 中详细介绍。

外部连接和全连接

Select.join()Select.join_from() 方法都接受关键字参数Select.join.isouterSelect.join.full,它们将分别呈现 LEFT OUTER JOIN 和 FULL OUTER JOIN。

>>> print(select(user_table).join(address_table, isouter=True))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id
>>> print(select(user_table).join(address_table, full=True))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id

还有一个方法Select.outerjoin() 等效于使用.join(..., isouter=True)

提示

SQL 还有一个“RIGHT OUTER JOIN”。SQLAlchemy 不会直接呈现它;相反,请反转表的顺序并使用“LEFT OUTER JOIN”。

ORDER BY、GROUP BY、HAVING

SELECT SQL 语句包含一个名为 ORDER BY 的子句,用于按给定顺序返回选定的行。

GROUP BY 子句的构建方式类似于 ORDER BY 子句,其目的是将选定的行细分为特定的组,然后可以在这些组上调用聚合函数。HAVING 子句通常与 GROUP BY 一起使用,其形式类似于 WHERE 子句,但它适用于组内使用的聚合函数。

ORDER BY

ORDER BY 子句是根据 SQL 表达式结构构建的,这些结构通常基于Column 或类似的对象。Select.order_by() 方法按位置接受一个或多个这些表达式。

>>> print(select(user_table).order_by(user_table.c.name))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account ORDER BY user_account.name

升序/降序可以通过ColumnElement.asc()ColumnElement.desc() 修饰符获得,这些修饰符也存在于 ORM 绑定的属性中。

>>> print(select(User).order_by(User.fullname.desc()))
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account ORDER BY user_account.fullname DESC

上面的语句将生成按user_account.fullname 列降序排列的行。

带有 GROUP BY / HAVING 的聚合函数

在 SQL 中,聚合函数允许跨多行的列表达式聚合在一起以产生单个结果。示例包括计数、计算平均值以及查找一组值中的最大值或最小值。

SQLAlchemy 使用名为 func 的命名空间以开放式方式提供 SQL 函数。这是一个特殊的构造器对象,它在给出特定 SQL 函数的名称时会创建新的 Function 实例,该函数可以有任意名称,以及传递给函数的零个或多个参数,就像在所有其他情况下一样,是 SQL 表达式结构。例如,要针对 user_account.id 列渲染 SQL COUNT() 函数,我们调用 count() 名称

>>> from sqlalchemy import func
>>> count_fn = func.count(user_table.c.id)
>>> print(count_fn)
count(user_account.id)

在本教程后面的 使用 SQL 函数 中将更详细地介绍 SQL 函数。

在 SQL 中使用聚合函数时,GROUP BY 子句必不可少,因为它允许将行划分为组,聚合函数将分别应用于每个组。当在 SELECT 语句的 COLUMNS 子句中请求非聚合列时,SQL 要求这些列都受 GROUP BY 子句约束,无论是直接还是间接基于主键关联。HAVING 子句与 WHERE 子句的使用方式类似,不同之处在于它根据聚合值而不是直接行内容过滤掉行。

SQLAlchemy 使用 Select.group_by()Select.having() 方法来提供这两个子句。下面我们说明选择用户名字段以及地址计数,对于那些拥有多个地址的用户。

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(User.name, func.count(Address.id).label("count"))
...         .join(Address)
...         .group_by(User.name)
...         .having(func.count(Address.id) > 1)
...     )
...     print(result.all())
BEGIN (implicit) SELECT user_account.name, count(address.id) AS count FROM user_account JOIN address ON user_account.id = address.user_id GROUP BY user_account.name HAVING count(address.id) > ? [...] (1,)
[('sandy', 2)]
ROLLBACK

按标签排序或分组

一项重要的技术,特别是在某些数据库后端上,能够按已经在列子句中声明的表达式进行 ORDER BY 或 GROUP BY,而无需在 ORDER BY 或 GROUP BY 子句中重新声明表达式,而是使用来自 COLUMNS 子句的列名或标记名。这种形式可以通过将名称的字符串文本传递给 Select.order_by()Select.group_by() 方法来实现。传递的文本 **不会直接渲染**;相反,将给定表达式的名称传递给列子句,并在上下文中以该表达式名称渲染,如果没有找到匹配项,则会引发错误。一元修饰符 asc()desc() 也可用于此形式。

>>> from sqlalchemy import func, desc
>>> stmt = (
...     select(Address.user_id, func.count(Address.id).label("num_addresses"))
...     .group_by("user_id")
...     .order_by("user_id", desc("num_addresses"))
... )
>>> print(stmt)
SELECT address.user_id, count(address.id) AS num_addresses FROM address GROUP BY address.user_id ORDER BY address.user_id, num_addresses DESC

使用别名

现在我们正在从多个表中选择并使用联接,我们很快就会遇到需要在语句的 FROM 子句中多次引用同一个表的情况。我们使用 SQL **别名** 来完成此操作,别名是一种语法,它为表或子查询提供备用名称,可在语句中引用它。

在 SQLAlchemy 表达式语言中,这些“名称”由 FromClause 对象表示,称为 Alias 结构,它是使用 Core 中的 FromClause.alias() 方法构造的。一个 Alias 结构就像一个 Table 结构一样,它还在 Alias.c 集合中拥有一个 Column 对象的命名空间。例如,下面的 SELECT 语句返回所有唯一的用户名对。

>>> user_alias_1 = user_table.alias()
>>> user_alias_2 = user_table.alias()
>>> print(
...     select(user_alias_1.c.name, user_alias_2.c.name).join_from(
...         user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id
...     )
... )
SELECT user_account_1.name, user_account_2.name AS name_1 FROM user_account AS user_account_1 JOIN user_account AS user_account_2 ON user_account_1.id > user_account_2.id

ORM 实体别名

ORM 中的 FromClause.alias() 方法的等效方法是 ORM 的 aliased() 函数,它可以应用于实体,例如 UserAddress。这会在内部生成一个 Alias 对象,该对象针对原始映射的 Table 对象,同时保持 ORM 功能。下面的 SELECT 从 User 实体中选择所有包含两个特定电子邮件地址的对象。

>>> from sqlalchemy.orm import aliased
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> print(
...     select(User)
...     .join_from(User, address_alias_1)
...     .where(address_alias_1.email_address == "[email protected]")
...     .join_from(User, address_alias_2)
...     .where(address_alias_2.email_address == "[email protected]")
... )
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

提示

设置 ON 子句 中所述,ORM 提供了另一种使用 relationship() 结构进行联接的方式。使用别名的上述示例使用 relationship()使用 Relationship 在别名目标之间联接 中进行了演示。

子查询和 CTE

SQL 中的子查询是一个 SELECT 语句,它在括号内渲染并放置在封闭语句的上下文中,通常是一个 SELECT 语句,但并非必须。

本节将介绍所谓的“非标量”子查询,它通常放置在封闭 SELECT 语句的 FROM 子句中。我们还将介绍公用表表达式或 CTE,它与子查询的使用方式类似,但包含更多功能。

SQLAlchemy 使用 Subquery 对象来表示子查询,使用 CTE 对象来表示 CTE,通常分别从 Select.subquery()Select.cte() 方法获得。这两个对象都可以用作大型 select() 结构中的 FROM 元素。

我们可以构造一个 Subquery,它将从 address 表中选择行的聚合计数(之前在 使用 GROUP BY / HAVING 的聚合函数 中介绍了聚合函数和 GROUP BY)。

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .subquery()
... )

在没有嵌入到另一个 Select 或其他语句中的情况下,将子查询本身字符串化会生成没有任何封闭括号的纯 SELECT 语句。

>>> print(subq)
SELECT count(address.id) AS count, address.user_id FROM address GROUP BY address.user_id

Subquery 对象的行为类似于其他任何 FROM 对象,例如 Table,特别地,它包含它选择的列的 Subquery.c 命名空间。我们可以使用此命名空间来引用 user_id 列以及我们自定义标记的 count 表达式。

>>> print(select(subq.c.user_id, subq.c.count))
SELECT anon_1.user_id, anon_1.count FROM (SELECT count(address.id) AS count, address.user_id AS user_id FROM address GROUP BY address.user_id) AS anon_1

使用 subq 对象中包含的一组行,我们可以将该对象应用于一个更大的 Select,它将数据联接到 user_account 表。

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
SELECT user_account.name, user_account.fullname, anon_1.count FROM user_account JOIN (SELECT count(address.id) AS count, address.user_id AS user_id FROM address GROUP BY address.user_id) AS anon_1 ON user_account.id = anon_1.user_id

为了从 user_account 联接到 address,我们使用了 Select.join_from() 方法。如前所述,此联接的 ON 子句又是 **推断** 出来的,基于外键约束。尽管 SQL 子查询本身没有任何约束,但 SQLAlchemy 可以作用于列上表示的约束,通过确定 subq.c.user_id 列 **派生** 自 address_table.c.user_id 列,而该列确实表示回 user_table.c.id 列的外键关系,然后用于生成 ON 子句。

公用表表达式 (CTE)

在 SQLAlchemy 中使用 CTE 结构与使用 Subquery 结构的方式几乎相同。通过将 Select.subquery() 方法的调用改为使用 Select.cte(),我们可以以相同的方式使用生成的 对象作为 FROM 元素,但渲染的 SQL 是完全不同的通用表表达式语法。

>>> subq = (
...     select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .cte()
... )

>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
...     user_table, subq
... )

>>> print(stmt)
WITH anon_1 AS (SELECT count(address.id) AS count, address.user_id AS user_id FROM address GROUP BY address.user_id) SELECT user_account.name, user_account.fullname, anon_1.count FROM user_account JOIN anon_1 ON user_account.id = anon_1.user_id

CTE 结构还具有以“递归”方式使用的功能,并且在更复杂的情况下,可以从 INSERT、UPDATE 或 DELETE 语句的 RETURNING 子句中组合。有关这些附加模式的详细信息,请参阅 CTE 的文档字符串。

在这两种情况下,子查询和 CTE 都使用“匿名”名称在 SQL 级别命名。在 Python 代码中,我们根本不需要提供这些名称。在渲染时,SubqueryCTE 实例的对象标识用作对象的语法标识。可以通过将名称作为 Select.subquery()Select.cte() 方法的第一个参数传递来提供将在 SQL 中渲染的名称。

另请参阅

Select.subquery() - 子查询的更多详细信息

Select.cte() - CTE 的示例,包括如何使用 RECURSIVE 以及面向 DML 的 CTE

ORM 实体子查询/CTE

在 ORM 中,aliased() 结构可用于将 ORM 实体(如我们的 UserAddress 类)与任何表示行源的 FromClause 概念相关联。前面的小节 ORM 实体别名 说明了如何使用 aliased() 将映射类与映射 TableAlias 相关联。这里说明了 aliased()Subquery 和针对 Select 结构生成的 CTE 执行相同的操作,最终派生自同一个映射的 Table

下面是将 aliased() 应用于 Subquery 结构的示例,以便可以从其行中提取 ORM 实体。结果显示了一系列 UserAddress 对象,其中每个 Address 对象的数据最终来自针对 address 表的子查询,而不是该表本身。

>>> subq = select(Address).where(~Address.email_address.like("%@aol.com")).subquery()
>>> address_subq = aliased(Address, subq)
>>> stmt = (
...     select(User, address_subq)
...     .join_from(User, address_subq)
...     .order_by(User.id, address_subq.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname, anon_1.id AS id_1, anon_1.email_address, anon_1.user_id FROM user_account JOIN (SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id FROM address WHERE address.email_address NOT LIKE ?) AS anon_1 ON user_account.id = anon_1.user_id ORDER BY user_account.id, anon_1.id [...] ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='[email protected]') User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='[email protected]') User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='[email protected]')
ROLLBACK

接下来是另一个示例,它与上一个示例完全相同,只是它使用了 CTE 结构,而不是 Subquery 结构。

>>> cte_obj = select(Address).where(~Address.email_address.like("%@aol.com")).cte()
>>> address_cte = aliased(Address, cte_obj)
>>> stmt = (
...     select(User, address_cte)
...     .join_from(User, address_cte)
...     .order_by(User.id, address_cte.id)
... )
>>> with Session(engine) as session:
...     for user, address in session.execute(stmt):
...         print(f"{user} {address}")
BEGIN (implicit) WITH anon_1 AS (SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id FROM address WHERE address.email_address NOT LIKE ?) SELECT user_account.id, user_account.name, user_account.fullname, anon_1.id AS id_1, anon_1.email_address, anon_1.user_id FROM user_account JOIN anon_1 ON user_account.id = anon_1.user_id ORDER BY user_account.id, anon_1.id [...] ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='[email protected]') User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='[email protected]') User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='[email protected]')
ROLLBACK

标量子查询和相关子查询

标量子查询是一个子查询,它恰好返回零行或一行,以及恰好一列。然后,子查询在封闭 SELECT 语句的 COLUMNS 或 WHERE 子句中使用,它与常规子查询的不同之处在于它不用于 FROM 子句。一个 相关子查询 是一个标量子查询,它引用封闭 SELECT 语句中的表。

SQLAlchemy 使用 ScalarSelect 结构表示标量子查询,它是 ColumnElement 表达式层次结构的一部分,与常规子查询不同,常规子查询由 Subquery 结构表示,该结构位于 FromClause 层次结构中。

标量子查询通常(但并非必须)与聚合函数一起使用,聚合函数在 使用 GROUP BY/HAVING 的聚合函数 中介绍过。标量子查询通过使用 Select.scalar_subquery() 方法来显式指示,如下所示。它在被自身字符串化时的默认字符串形式呈现为一个普通的 SELECT 语句,该语句从两个表中选择。

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .scalar_subquery()
... )
>>> print(subq)
(SELECT count(address.id) AS count_1 FROM address, user_account WHERE user_account.id = address.user_id)

上面的 subq 对象现在位于 ColumnElement SQL 表达式层次结构中,因为它可以像任何其他列表达式一样使用。

>>> print(subq == 5)
(SELECT count(address.id) AS count_1 FROM address, user_account WHERE user_account.id = address.user_id) = :param_1

尽管标量子查询本身在被自身字符串化时会在其 FROM 子句中渲染 user_accountaddress,但在将其嵌入到处理 user_account 表的封闭 select() 结构中时,user_account 表会自动 **相关**,这意味着它不会在子查询的 FROM 子句中渲染。

>>> stmt = select(user_table.c.name, subq.label("address_count"))
>>> print(stmt)
SELECT user_account.name, (SELECT count(address.id) AS count_1 FROM address WHERE user_account.id = address.user_id) AS address_count FROM user_account

简单的相关子查询通常会执行所需的操作。但是,在相关性不明确的情况下,SQLAlchemy 会告诉我们需要更多清晰度。

>>> stmt = (
...     select(
...         user_table.c.name,
...         address_table.c.email_address,
...         subq.label("address_count"),
...     )
...     .join_from(user_table, address_table)
...     .order_by(user_table.c.id, address_table.c.id)
... )
>>> print(stmt)
Traceback (most recent call last):
...
InvalidRequestError: Select statement '<... Select object at ...>' returned
no FROM clauses due to auto-correlation; specify correlate(<tables>) to
control correlation manually.

为了指定 user_table 是我们想要关联的表,我们可以使用 ScalarSelect.correlate()ScalarSelect.correlate_except() 方法。

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .scalar_subquery()
...     .correlate(user_table)
... )

然后,该语句可以像任何其他列一样返回此列的数据。

>>> with engine.connect() as conn:
...     result = conn.execute(
...         select(
...             user_table.c.name,
...             address_table.c.email_address,
...             subq.label("address_count"),
...         )
...         .join_from(user_table, address_table)
...         .order_by(user_table.c.id, address_table.c.id)
...     )
...     print(result.all())
BEGIN (implicit) SELECT user_account.name, address.email_address, (SELECT count(address.id) AS count_1 FROM address WHERE user_account.id = address.user_id) AS address_count FROM user_account JOIN address ON user_account.id = address.user_id ORDER BY user_account.id, address.id [...] ()
[('spongebob', '[email protected]', 1), ('sandy', '[email protected]', 2), ('sandy', '[email protected]', 2)]
ROLLBACK

LATERAL 相关性

LATERAL 相关性是 SQL 相关性的一种特殊子类别,它允许一个可选择单元引用单个 FROM 子句中的另一个可选择单元。这是一种非常特殊的用例,虽然它是 SQL 标准的一部分,但据我们所知,只有最新版本的 PostgreSQL 支持。

通常,如果 SELECT 语句在 FROM 子句中引用 table1 JOIN (SELECT ...) AS subquery,则右侧的子查询可能不会引用左侧的 “table1” 表达式;关联只能引用属于完全包围此 SELECT 的另一个 SELECT 的表。LATERAL 关键字允许我们改变这种行为,并允许从右侧 JOIN 中进行关联。

SQLAlchemy 使用 Select.lateral() 方法支持此功能,该方法创建一个称为 Lateral 的对象。 LateralSubqueryAlias 属于同一家族,但在将结构添加到封闭 SELECT 的 FROM 子句时,还包含关联行为。以下示例说明了一个使用 LATERAL 的 SQL 查询,该查询选择上一节中讨论的 “用户帐户 / 电子邮件地址数量” 数据

>>> subq = (
...     select(
...         func.count(address_table.c.id).label("address_count"),
...         address_table.c.email_address,
...         address_table.c.user_id,
...     )
...     .where(user_table.c.id == address_table.c.user_id)
...     .lateral()
... )
>>> stmt = (
...     select(user_table.c.name, subq.c.address_count, subq.c.email_address)
...     .join_from(user_table, subq)
...     .order_by(user_table.c.id, subq.c.email_address)
... )
>>> print(stmt)
SELECT user_account.name, anon_1.address_count, anon_1.email_address FROM user_account JOIN LATERAL (SELECT count(address.id) AS address_count, address.email_address AS email_address, address.user_id AS user_id FROM address WHERE user_account.id = address.user_id) AS anon_1 ON user_account.id = anon_1.user_id ORDER BY user_account.id, anon_1.email_address

上面,JOIN 的右侧是一个与 JOIN 左侧的 user_account 表相关的子查询。

当使用 Select.lateral() 时, Select.correlate()Select.correlate_except() 方法的行为也适用于 Lateral 结构。

另请参阅

Lateral

Select.lateral()

UNION、UNION ALL 和其他集合操作

在 SQL 中,SELECT 语句可以使用 UNION 或 UNION ALL SQL 操作合并在一起,这将生成由一个或多个语句一起产生的所有行的集合。其他集合操作,例如 INTERSECT [ALL] 和 EXCEPT [ALL] 也是可能的。

SQLAlchemy 的 Select 结构使用诸如 union()intersect()except_() 以及 “all” 对应项 union_all()intersect_all()except_all() 的函数支持这种性质的组合。这些函数都接受任意数量的子选择器,这些子选择器通常是 Select 结构,但也可能是一个现有的组合。

由这些函数产生的结构是 CompoundSelect,它与 Select 结构的使用方式相同,只是它具有的方法更少。例如,由 union_all() 生成的 CompoundSelect 可以直接使用 Connection.execute() 调用

>>> from sqlalchemy import union_all
>>> stmt1 = select(user_table).where(user_table.c.name == "sandy")
>>> stmt2 = select(user_table).where(user_table.c.name == "spongebob")
>>> u = union_all(stmt1, stmt2)
>>> with engine.connect() as conn:
...     result = conn.execute(u)
...     print(result.all())
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? UNION ALL SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? [generated in ...] ('sandy', 'spongebob')
[(2, 'sandy', 'Sandy Cheeks'), (1, 'spongebob', 'Spongebob Squarepants')]
ROLLBACK

要将 CompoundSelect 作为子查询使用,就像 Select 一样,它提供了一个 SelectBase.subquery() 方法,该方法将生成一个 Subquery 对象,该对象具有一个 FromClause.c 集合,该集合可以在封闭的 select() 中引用。

>>> u_subq = u.subquery()
>>> stmt = (
...     select(u_subq.c.name, address_table.c.email_address)
...     .join_from(address_table, u_subq)
...     .order_by(u_subq.c.name, address_table.c.email_address)
... )
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN (implicit) SELECT anon_1.name, address.email_address FROM address JOIN (SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname FROM user_account WHERE user_account.name = ? UNION ALL SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname FROM user_account WHERE user_account.name = ?) AS anon_1 ON anon_1.id = address.user_id ORDER BY anon_1.name, address.email_address [generated in ...] ('sandy', 'spongebob')
[('sandy', '[email protected]'), ('sandy', '[email protected]'), ('spongebob', '[email protected]')]
ROLLBACK

从联合中选择 ORM 实体

前面的示例说明了如何构造给定两个 Table 对象的 UNION,然后返回数据库行。如果我们想要使用 UNION 或其他集合操作来选择然后作为 ORM 对象接收的行,可以使用两种方法。在这两种情况下,我们首先构造一个 select()CompoundSelect 对象,该对象代表我们要执行的 SELECT / UNION / 等语句;此语句应针对目标 ORM 实体或其底层映射的 Table 对象进行组合。

>>> stmt1 = select(User).where(User.name == "sandy")
>>> stmt2 = select(User).where(User.name == "spongebob")
>>> u = union_all(stmt1, stmt2)

对于一个简单的 SELECT 与 UNION,它没有嵌套在子查询中,这些通常可以通过使用 Select.from_statement() 方法在 ORM 对象获取上下文中使用。使用这种方法,UNION 语句表示整个查询;在使用 Select.from_statement() 之后,无法添加其他条件。

>>> orm_stmt = select(User).from_statement(u)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? UNION ALL SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? [generated in ...] ('sandy', 'spongebob')
User(id=2, name='sandy', fullname='Sandy Cheeks') User(id=1, name='spongebob', fullname='Spongebob Squarepants')
ROLLBACK

要以更灵活的方式使用 UNION 或其他与集合相关的结构作为实体相关的组件,可以将 CompoundSelect 结构组织成一个使用 CompoundSelect.subquery() 的子查询,然后使用 aliased() 函数链接到 ORM 对象。这与 ORM 实体子查询/CTE 中介绍的方式相同,首先为我们的目标实体创建 ad-hoc “映射” 到子查询,然后从该新实体中选择,就好像它是任何其他映射的类一样。在下面的示例中,我们能够添加额外的条件,例如 UNION 本身之外的 ORDER BY,因为我们可以根据子查询导出的列进行过滤或排序。

>>> user_alias = aliased(User, u.subquery())
>>> orm_stmt = select(user_alias).order_by(user_alias.id)
>>> with Session(engine) as session:
...     for obj in session.execute(orm_stmt).scalars():
...         print(obj)
BEGIN (implicit) 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.name = ? UNION ALL SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname FROM user_account WHERE user_account.name = ?) AS anon_1 ORDER BY anon_1.id [generated in ...] ('sandy', 'spongebob')
User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=2, name='sandy', fullname='Sandy Cheeks')
ROLLBACK

EXISTS 子查询

SQL EXISTS 关键字是一个运算符,它与 标量子查询 一起使用,以返回一个布尔值 true 或 false,具体取决于 SELECT 语句是否会返回一行。SQLAlchemy 包含 ScalarSelect 对象的变体称为 Exists,它将生成一个 EXISTS 子查询,最方便地使用 SelectBase.exists() 方法生成。下面我们生成一个 EXISTS,以便我们可以返回在 address 中具有两个以上相关行的 user_account 行。

>>> subq = (
...     select(func.count(address_table.c.id))
...     .where(user_table.c.id == address_table.c.user_id)
...     .group_by(address_table.c.user_id)
...     .having(func.count(address_table.c.id) > 1)
... ).exists()
>>> with engine.connect() as conn:
...     result = conn.execute(select(user_table.c.name).where(subq))
...     print(result.all())
BEGIN (implicit) SELECT user_account.name FROM user_account WHERE EXISTS (SELECT count(address.id) AS count_1 FROM address WHERE user_account.id = address.user_id GROUP BY address.user_id HAVING count(address.id) > ?) [...] (1,)
[('sandy',)]
ROLLBACK

EXISTS 结构通常用作否定,例如 NOT EXISTS,因为它提供了一种 SQL 高效的形式来定位相关表没有行的行。下面我们选择没有电子邮件地址的用户名;请注意第二个 WHERE 子句中使用的二元否定运算符 (~)

>>> subq = (
...     select(address_table.c.id).where(user_table.c.id == address_table.c.user_id)
... ).exists()
>>> with engine.connect() as conn:
...     result = conn.execute(select(user_table.c.name).where(~subq))
...     print(result.all())
BEGIN (implicit) SELECT user_account.name FROM user_account WHERE NOT (EXISTS (SELECT address.id FROM address WHERE user_account.id = address.user_id)) [...] ()
[('patrick',)]
ROLLBACK

使用 SQL 函数

在本节前面介绍的 使用 GROUP BY / HAVING 的聚合函数 中,func 对象用作创建新的 Function 对象的工厂,这些对象在类似于 select() 的结构中使用时,会生成一个 SQL 函数显示,通常包含一个名称、一些括号(尽管并不总是如此)以及一些参数。典型的 SQL 函数示例包括

  • count() 函数,一个聚合函数,用于统计返回的行数

    >>> print(select(func.count()).select_from(user_table))
    
    SELECT count(*) AS count_1 FROM user_account
  • lower() 函数,一个字符串函数,用于将字符串转换为小写

    >>> print(select(func.lower("A String With Much UPPERCASE")))
    
    SELECT lower(:lower_2) AS lower_1
  • now() 函数,用于提供当前日期和时间;由于这是一个常见的函数, SQLAlchemy 知道如何在不同的后端渲染它,例如在 SQLite 中使用 CURRENT_TIMESTAMP 函数

    >>> stmt = select(func.now())
    >>> with engine.connect() as conn:
    ...     result = conn.execute(stmt)
    ...     print(result.all())
    
    BEGIN (implicit) SELECT CURRENT_TIMESTAMP AS now_1 [...] () [(datetime.datetime(...),)] ROLLBACK

由于大多数数据库后端都有数十甚至数百个不同的 SQL 函数,func 尝试尽可能地接受它所接受的内容。从该命名空间访问的任何名称都会自动被认为是 SQL 函数,它将以通用的方式呈现

>>> print(select(func.some_crazy_function(user_table.c.name, 17)))
SELECT some_crazy_function(user_account.name, :some_crazy_function_2) AS some_crazy_function_1 FROM user_account

与此同时,一小部分非常常见的 SQL 函数,例如 countnowmaxconcat 包含预先打包的版本,这些版本提供适当的类型信息,并且在某些情况下还提供后端特定的 SQL 生成。下面的示例对比了 PostgreSQL 方言和 Oracle 方言对 now 函数的 SQL 生成

>>> from sqlalchemy.dialects import postgresql
>>> print(select(func.now()).compile(dialect=postgresql.dialect()))
SELECT now() AS now_1
>>> from sqlalchemy.dialects import oracle >>> print(select(func.now()).compile(dialect=oracle.dialect()))
SELECT CURRENT_TIMESTAMP AS now_1 FROM DUAL

函数有返回值

由于函数是列表达式,因此它们也有 SQL 数据类型 用于描述生成的 SQL 表达式的类型。我们在这里将这些类型称为“SQL 返回类型”,指的是函数在数据库端 SQL 表达式的上下文中返回的 SQL 值的类型,而不是 Python 函数的“返回类型”。

任何 SQL 函数的 SQL 返回类型都可以访问,通常用于调试目的,方法是引用 Function.type 属性;这将为极少数非常常见的 SQL 函数预先配置,但对于大多数 SQL 函数来说,如果未另外指定,则为“空”数据类型

>>> # pre-configured SQL function (only a few dozen of these)
>>> func.now().type
DateTime()

>>> # arbitrary SQL function (all other SQL functions)
>>> func.run_some_calculation().type
NullType()

这些 SQL 返回类型在将函数表达式用于更大的表达式时非常重要;也就是说,当表达式的类型类似于 IntegerNumeric 时,数学运算符会更好地工作,为了使 JSON 访问器工作,需要使用类似于 JSON 的类型。某些类型的函数会返回整个行而不是列值,在这种情况下需要引用特定的列;这样的函数被称为 表值函数

在执行语句并获取返回行时,函数的 SQL 返回类型也可能很重要,对于 SQLAlchemy 必须应用结果集处理的情况。一个典型示例是 SQLite 上的日期相关函数,其中 SQLAlchemy 的 DateTime 和相关数据类型负责将字符串值转换为 Python datetime() 对象,因为接收到了结果行。

要将特定类型应用于正在创建的函数,可以使用 Function.type_ 参数传递它;类型参数可以是 TypeEngine 类或实例。在下面的示例中,我们将 JSON 类传递给生成 PostgreSQL json_object() 函数,注意 SQL 返回类型将是 JSON 类型

>>> from sqlalchemy import JSON
>>> function_expr = func.json_object('{a, 1, b, "def", c, 3.5}', type_=JSON)

通过使用 JSON 数据类型创建 JSON 函数,SQL 表达式对象将具有 JSON 相关功能,例如访问元素

>>> stmt = select(function_expr["def"])
>>> print(stmt)
SELECT json_object(:json_object_1)[:json_object_2] AS anon_1

内置函数具有预先配置的返回类型

对于像 countmaxmin 这样的常见聚合函数以及非常少量的日期函数,比如 now 和字符串函数,比如 concat,SQL 返回类型被适当地设置,有时基于用法。 max 函数和类似的聚合过滤函数将根据给定的参数设置 SQL 返回类型

>>> m1 = func.max(Column("some_int", Integer))
>>> m1.type
Integer()

>>> m2 = func.max(Column("some_str", String))
>>> m2.type
String()

日期和时间函数通常对应于由 DateTimeDateTime 描述的 SQL 表达式

>>> func.now().type
DateTime()
>>> func.current_date().type
Date()

已知的字符串函数,如 concat,将知道 SQL 表达式将是 String 类型

>>> func.concat("x", "y").type
String()

但是,对于绝大多数 SQL 函数, SQLAlchemy 并没有将它们明确地包含在其非常小的已知函数列表中。例如,虽然通常使用 SQL 函数 func.lower()func.upper() 转换字符串的大小写并没有问题,但 SQLAlchemy 实际上并不了解这些函数,因此它们具有“空”SQL 返回类型

>>> func.upper("lowercase").type
NullType()

对于像 upperlower 这样的简单函数,问题通常并不严重,因为字符串值可以从数据库中接收,而 SQLAlchemy 方面没有特殊类型的处理,并且 SQLAlchemy 的类型强制规则通常可以正确地猜测意图;例如,Python + 运算符将根据表达式两侧的观察结果被正确地解释为字符串连接运算符

>>> print(select(func.upper("lowercase") + " suffix"))
SELECT upper(:upper_1) || :upper_2 AS anon_1

总的来说,Function.type_ 参数可能需要的情况是

  1. 该函数不是 SQLAlchemy 内置函数;这可以通过创建函数并观察 Function.type 属性来证明,即

    >>> func.count().type
    Integer()

    对比

    >>> func.json_object('{"a", "b"}').type
    NullType()
  2. 需要支持感知函数的表达式;这通常指的是与诸如 JSONARRAY 这样的数据类型相关的特殊运算符

  3. 需要对结果值进行处理,这可能包括 DateTimeBooleanEnum 等类型,以及 JSONARRAY 等特殊数据类型。

高级 SQL 函数技巧

以下小节将说明使用 SQL 函数可以实现更多功能。虽然这些技巧比基本的 SQL 函数使用更少见且更高级,但它们仍然非常流行,这很大程度上是由于 PostgreSQL 对更复杂函数形式的重视,包括表值和列值形式,这些形式在 JSON 数据中很流行。

使用窗口函数

窗口函数是 SQL 聚合函数的一种特殊用法,它在处理单个结果行时计算组中返回行的聚合值。而像 MAX() 这样的函数将为您提供一组行中列的最高值,而使用相同的函数作为“窗口函数”将为您提供每行的最高值,直到该行为止

在 SQL 中,窗口函数允许您指定函数应应用于哪些行,一个“分区”值,它考虑不同行子集上的窗口,以及一个“排序”表达式,它重要的是指示应将行应用于聚合函数的顺序。

在 SQLAlchemy 中,由 func 命名空间生成的 SQL 函数都包含一个 FunctionElement.over() 方法,它授予窗口函数或“OVER”语法;产生的结构是 Over 结构。

与窗口函数一起使用的常见函数是 row_number() 函数,它只是对行进行计数。我们可以根据用户名对该行计数进行分区,以便对单个用户的电子邮件地址进行编号。

>>> stmt = (
...     select(
...         func.row_number().over(partition_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN (implicit) SELECT row_number() OVER (PARTITION BY user_account.name) AS anon_1, user_account.name, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id [...] ()
[(1, 'sandy', '[email protected]'), (2, 'sandy', '[email protected]'), (1, 'spongebob', '[email protected]')]
ROLLBACK

上面,FunctionElement.over.partition_by 参数用于使 PARTITION BY 子句在 OVER 子句中呈现。我们还可以使用 FunctionElement.over.order_by 来使用 ORDER BY 子句。

>>> stmt = (
...     select(
...         func.count().over(order_by=user_table.c.name),
...         user_table.c.name,
...         address_table.c.email_address,
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN (implicit) SELECT count(*) OVER (ORDER BY user_account.name) AS anon_1, user_account.name, address.email_address FROM user_account JOIN address ON user_account.id = address.user_id [...] ()
[(2, 'sandy', '[email protected]'), (2, 'sandy', '[email protected]'), (3, 'spongebob', '[email protected]')]
ROLLBACK

窗口函数的其他选项包括范围的用法;有关更多示例,请参阅 over()

提示

重要的是要注意,FunctionElement.over() 方法仅适用于那些实际上是聚合函数的 SQL 函数;虽然 Over 结构将很乐意为给定的任何 SQL 函数呈现自身,但如果函数本身不是 SQL 聚合函数,则数据库将拒绝表达式。

特殊修饰符 WITHIN GROUP、FILTER

“WITHIN GROUP” SQL 语法与“有序集”或“假设集”聚合函数一起使用。常见的“有序集”函数包括 percentile_cont()rank()。SQLAlchemy 包含内置实现 rankdense_rankmodepercentile_contpercentile_disc,它们包含一个 FunctionElement.within_group() 方法。

>>> print(
...     func.unnest(
...         func.percentile_disc([0.25, 0.5, 0.75, 1]).within_group(user_table.c.name)
...     )
... )
unnest(percentile_disc(:percentile_disc_1) WITHIN GROUP (ORDER BY user_account.name))

一些后端支持“FILTER”以将聚合函数的范围限制为与返回行的总范围相比的特定行子集,可通过 FunctionElement.filter() 方法使用。

>>> stmt = (
...     select(
...         func.count(address_table.c.email_address).filter(user_table.c.name == "sandy"),
...         func.count(address_table.c.email_address).filter(
...             user_table.c.name == "spongebob"
...         ),
...     )
...     .select_from(user_table)
...     .join(address_table)
... )
>>> with engine.connect() as conn:  
...     result = conn.execute(stmt)
...     print(result.all())
BEGIN (implicit) SELECT count(address.email_address) FILTER (WHERE user_account.name = ?) AS anon_1, count(address.email_address) FILTER (WHERE user_account.name = ?) AS anon_2 FROM user_account JOIN address ON user_account.id = address.user_id [...] ('sandy', 'spongebob')
[(2, 1)]
ROLLBACK

表值函数

表值 SQL 函数支持包含命名子元素的标量表示形式。表值函数通常用于 JSON 和面向数组的函数以及 generate_series() 等函数,在 FROM 子句中指定,然后作为表引用,有时甚至作为列引用。这种形式的函数在 PostgreSQL 数据库中很突出,但是 SQLite、Oracle 和 SQL Server 也支持某些形式的表值函数。

另请参阅

表值、表值和列值函数、行和元组对象 - 在 PostgreSQL 文档中。

虽然许多数据库支持表值和其他特殊形式,但 PostgreSQL 往往是这些功能需求最大的地方。有关 PostgreSQL 语法和更多功能的附加示例,请参阅本节。

SQLAlchemy 提供 FunctionElement.table_valued() 方法作为基本“表值函数”结构,它将 func 对象转换为包含一系列命名列的 FROM 子句,这些列基于位置传递的字符串名称。这返回一个 TableValuedAlias 对象,它是一个支持函数的 Alias 结构,可以像 使用别名 中介绍的任何其他 FROM 子句一样使用。下面我们说明 json_each() 函数,虽然它在 PostgreSQL 中很常见,但也受现代版本的 SQLite 支持。

>>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value")
>>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"]))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN (implicit) SELECT anon_1.value FROM json_each(?) AS anon_1 WHERE anon_1.value IN (?, ?) [...] ('["one", "two", "three"]', 'two', 'three')
[('two',), ('three',)]
ROLLBACK

上面,我们使用 SQLite 和 PostgreSQL 支持的 json_each() JSON 函数生成一个表值表达式,其中包含一个名为 value 的列,然后选择了其三个行中的两个。

另请参阅

表值函数 - 在 PostgreSQL 文档中 - 本节将详细介绍其他语法,例如特殊列派生和“WITH ORDINALITY”,这些语法已知可用于 PostgreSQL。

列值函数 - 表值函数作为标量列

PostgreSQL 和 Oracle 支持的一种特殊语法是在 FROM 子句中引用函数,该函数随后在 SELECT 语句或其他列表达式上下文的列子句中将自身作为单个列传递。PostgreSQL 非常善于使用这种语法来处理 json_array_elements()json_object_keys()json_each_text()json_each() 等函数。

SQLAlchemy 将其称为“列值”函数,可以通过将 FunctionElement.column_valued() 修饰符应用于 Function 结构来使用。

>>> from sqlalchemy import select, func
>>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x"))
>>> print(stmt)
SELECT x FROM json_array_elements(:json_array_elements_1) AS x

Oracle 方言也支持“列值”形式,在 Oracle 方言中,它可用于自定义 SQL 函数。

>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
SELECT s.COLUMN_VALUE FROM TABLE (scalar_strings(:scalar_strings_1)) s

另请参阅

列值函数 - 在 PostgreSQL 文档中。

数据转换和类型强制转换

在 SQL 中,我们经常需要显式地指定表达式的 datatype,无论是为了告诉数据库在模棱两可的表达式中预期什么类型,还是在某些情况下,当我们想要将 SQL 表达式的隐含 datatype 转换为其他类型时。SQL CAST 关键字用于此任务,在 SQLAlchemy 中,它由 cast() 函数提供。此函数接受一个列表达式和一个 datatype 对象作为参数,如下所示,我们从 user_table.c.id 列对象生成一个 SQL 表达式 CAST(user_account.id AS VARCHAR)

>>> from sqlalchemy import cast
>>> stmt = select(cast(user_table.c.id, String))
>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     result.all()
BEGIN (implicit) SELECT CAST(user_account.id AS VARCHAR) AS id FROM user_account [...] ()
[('1',), ('2',), ('3',)]
ROLLBACK

cast() 函数不仅渲染 SQL CAST 语法,它还会生成一个 SQLAlchemy 列表达式,该表达式在 Python 端也将充当给定的 datatype。一个被 cast() 转换为 JSON 的字符串表达式将获得 JSON 下标和比较运算符,例如

>>> from sqlalchemy import JSON
>>> print(cast("{'a': 'b'}", JSON)["a"])
CAST(:param_1 AS JSON)[:param_2]

type_coerce() - 一个仅限 Python 的“转换”

有时需要让 SQLAlchemy 知道表达式的 datatype,出于上述所有原因,但不在 SQL 端渲染 CAST 表达式本身,因为它可能会干扰已经在没有它的情况下工作的 SQL 操作。对于这个相当常见的用例,还有一个函数 type_coerce()cast() 密切相关,因为它将 Python 表达式设置为具有特定的 SQL 数据库类型,但不会在数据库端渲染 CAST 关键字或 datatype。 type_coerce() 在处理 JSON datatype 时尤其重要,它通常与不同平台上的字符串导向 datatype 有着错综复杂的关系,甚至可能不是显式 datatype,例如在 SQLite 和 MariaDB 上。下面,我们使用 type_coerce() 将 Python 结构作为 JSON 字符串传递到 MySQL 的 JSON 函数之一中

>>> import json
>>> from sqlalchemy import JSON
>>> from sqlalchemy import type_coerce
>>> from sqlalchemy.dialects import mysql
>>> s = select(type_coerce({"some_key": {"foo": "bar"}}, JSON)["some_key"])
>>> print(s.compile(dialect=mysql.dialect()))
SELECT JSON_EXTRACT(%s, %s) AS anon_1

上面,MySQL 的 JSON_EXTRACT SQL 函数被调用,因为我们使用 type_coerce() 指示我们的 Python 字典应该被视为 JSON。Python __getitem__ 运算符,在本例中为 ['some_key'],因此成为可用,并允许渲染 JSON_EXTRACT 路径表达式(在本例中未显示,但最终将为 '$."some_key"')。