SqlAlchemy 2.0 中文文档(二)(1)https://developer.aliyun.com/article/1560323
LATERAL 相关性
LATERAL 相关性是 SQL 相关性的一种特殊子类,它允许可选单元在单个 FROM 子句内引用另一个可选单元。这是一个极其特殊的用例,虽然是 SQL 标准的一部分,但只有最近版本的 PostgreSQL 已知支持。
通常,如果 SELECT 语句在其 FROM 子句中引用了table1 JOIN (SELECT ...) AS subquery
,则右侧的子查询可能不会引用左侧的“table1”表达式;相关联可能只引用完全包围此 SELECT 的另一个 SELECT 的表。LATERAL 关键字允许我们改变这种行为,允许来自右侧 JOIN 的相关联。
SQLAlchemy 支持使用Select.lateral()
方法实现此功能,该方法创建一个称为Lateral
的对象。Lateral
与Subquery
和Alias
位于同一家族,但在将构造添加到封闭 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 的右侧是一个子查询,它与左侧的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()
方法,该方法将生成一个带有 FromClause.c
集合的 Subquery
对象,该集合可以在封闭的 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', 'sandy@sqlalchemy.org'), ('sandy', 'sandy@squirrelpower.org'), ('spongebob', 'spongebob@sqlalchemy.org')] ROLLBACK
从并集中选择 ORM 实体
前面的例子演示了如何构造一个 UNION,给定两个 Table
对象,然后返回数据库行。如果我们想要使用 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,如果它还没有嵌套在子查询中,那么可以经常在 ORM 对象获取的上下文中使用Select.from_statement()
方法。通过这种方法,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
构造将其组织到一个子查询中,然后使用aliased()
函数将其链接到 ORM 对象。这与在 ORM 实体子查询/CTEs 中引入的方式相同,首先创建我们想要的实体到子查询的临时“映射”,然后从新实体中选择,就像它是任何其他映射类一样。在下面的示例中,我们可以添加额外的条件,比如在 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
另请参阅
从 UNIONs 和其他集合操作中选择实体 - 在 ORM 查询指南中的 ## EXISTS 子查询
SQL EXISTS 关键字是与标量子查询一起使用的运算符,根据 SELECT 语句是否返回行来返回布尔值 true 或 false。SQLAlchemy 包含一个称为ScalarSelect
的对象变体,它将生成一个 EXISTS 子查询,并且最方便地使用SelectBase.exists()
方法生成。下面我们生成一个 EXISTS,以便我们可以返回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()` 函数,计算返回的行数的聚合函数: ```py >>> print(select(func.count()).select_from(user_table)) SELECT count(*) AS count_1 FROM user_account ``` + `lower()` 函数,将字符串转换为小写的字符串函数: ```py >>> print(select(func.lower("A String With Much UPPERCASE"))) SELECT lower(:lower_2) AS lower_1 ``` + `now()` 函数,提供当前日期和时间;由于这是一个常见的函数,SQLAlchemy 知道如何为每个后端呈现这个函数的不同表现形式,在 SQLite 中使用 CURRENT_TIMESTAMP 函数: ```py >>> 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 函数,将以一种通用的方式呈现: ```py >>> 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 函数,如count
、now
、max
、concat
包括它们自己的预打包版本,这些版本提供了适当的类型信息,并在某些情况下提供特定于后端的 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
属性来访问,通常用于调试目的:
>>> func.now().type DateTime()
这些 SQL 返回类型在将函数表达式用于更大表达式的上下文中时很重要;也就是说,数学运算符在表达式的数据类型为Integer
或Numeric
之类时效果更佳,为了使 JSON 访问器能够工作,需要使用诸如JSON
之类的类型。某些类别的函数返回整行而不是列值,需要引用特定列;这些函数被称为 table valued functions。
在执行语句并获取行时,函数的 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
内置函数具有预配置的返回类型
对于像count
、max
、min
等常见的聚合函数,以及非常少数的日期函数,比如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()
日期和时间函数通常对应于由DateTime
、Date
或Time
描述的 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 实际上并不知道这些函数,因此它们具有“null”SQL 返回类型:
>>> func.upper("lowercase").type NullType()
对于像upper
和lower
这样的简单函数,通常情况下问题不是很严重,因为字符串值可以在没有任何特殊类型处理的情况下从数据库接收,而且 SQLAlchemy 的类型转换规则通常也能够正确猜测意图;例如,Python 的+
操作符会根据表达式的两边正确解释为字符串连接操作符:
>>> print(select(func.upper("lowercase") + " suffix")) SELECT upper(:upper_1) || :upper_2 AS anon_1
总的来说,Function.type_
参数可能是必要的情况是:
- 如果函数不是 SQLAlchemy 的内置函数;这可以通过创建函数并观察
Function.type
属性来证明,即:
>>> func.count().type Integer()
- 与:
>>> func.json_object('{"a", "b"}').type NullType()
- 需要支持函数感知的表达式;这通常是指与诸如
JSON
或ARRAY
之类的数据类型相关的特殊操作符 - 需要结果值处理,其中可能包括诸如
DateTime
、Boolean
、Enum
或者再次是特殊的数据类型,如JSON
、ARRAY
。
高级 SQL 函数技术
以下各小节说明了可以使用 SQL 函数做的更多事情。虽然这些技术比基本的 SQL 函数使用更不常见且更高级,但它们仍然非常受欢迎,这在很大程度上是由于 PostgreSQL 强调更复杂的函数形式,包括与 JSON 数据流行的表和列值形式。
使用窗口函数
窗口函数是 SQL 聚合函数的特殊用法,它在处理个别结果行时计算在一组中返回的行上的聚合值。而像 MAX()
这样的函数将为你提供一组行中的列的最高值,使用相同函数作为“窗口函数”将为你提供每行的最高值,截至该行。
在 SQL 中,窗口函数允许指定应该应用函数的行、一个考虑不同行子集的“分区”值以及一个重要的指示行应该应用到聚合函数的顺序的“order by”表达式。
在 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', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (1, 'spongebob', 'spongebob@sqlalchemy.org')] 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', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (3, 'spongebob', 'spongebob@sqlalchemy.org')] ROLLBACK
窗口函数的更多选项包括使用范围;请参阅 over()
以获取更多示例。
提示
需要注意的是,FunctionElement.over()
方法仅适用于那些实际上是聚合函数的 SQL 函数;虽然 Over
结构会愉快地为任何给定的 SQL 函数渲染自己,但如果函数本身不是 SQL 聚合函数,数据库将拒绝该表达式。 #### 特殊修饰符 WITHIN GROUP, FILTER
“WITHIN GROUP” SQL 语法与“有序集合”或“假设集合”聚合函数一起使用。常见的“有序集合”函数包括percentile_cont()
和rank()
。SQLAlchemy 包含内置实现rank
, dense_rank
, mode
, percentile_cont
和 percentile_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 和 ARRAY 导向的函数以及像`generate_series()`这样的函数,表值函数在 FROM 子句中指定,然后被引用为表,有时甚至作为列。这种形式的函数在 PostgreSQL 数据库中非常突出,但某些形式的表值函数也受 SQLite、Oracle 和 SQL Server 支持。 另请参阅 表值、表和列值函数、行和元组对象 - 在 PostgreSQL 文档中。 虽然许多数据库支持表值和其他特殊形式,但 PostgreSQL 往往是对这些功能需求最大的地方。请参阅本节,了解 PostgreSQL 语法的附加示例以及其他功能。 SQLAlchemy 提供了 `FunctionElement.table_valued()` 方法作为基本的“表值函数”构造,它将一个 `func` 对象转换为一个包含一系列命名列的 FROM 子句,这些列是基于按位置传递的字符串名称。这将返回一个 `TableValuedAlias` 对象,它是一个启用函数的 `Alias` 构造,可像在 使用别名 中介绍的其他 FROM 子句一样使用。下面我们举例说明 `json_each()` 函数,尽管在 PostgreSQL 上很常见,但也受到现代版本的 SQLite 的支持: ```py >>> 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 方言的支持,可以用于自定义 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 中,我们经常需要明确指定表达式的数据类型,要么是为了告诉数据库在一个否则模棱两可的表达式中期望的类型是什么,要么是在某些情况下,当我们想要将 SQL 表达式的隐含数据类型转换为其他内容时。SQL CAST 关键字用于此任务,在 SQLAlchemy 中由cast()
函数提供。该函数接受列表达式和数据类型对象作为参数,如下所示,我们从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 端也将作为给定的数据类型。将字符串表达式cast()
到JSON
将获得 JSON 下标和比较运算符,例如:
>>> from sqlalchemy import JSON >>> print(cast("{'a': 'b'}", JSON)["a"]) CAST(:param_1 AS JSON)[:param_2]
type_coerce()
- 一个仅限于 Python 的“类型转换”函数
有时需要让 SQLAlchemy 知道表达式的数据类型,出于前述所有原因,但是不要在 SQL 端渲染 CAST 表达式本身,因为它可能会干扰已经正常工作的 SQL 操作。对于这种相当常见的用例,有另一个函数type_coerce()
,它与cast()
密切相关,它将设置一个 Python 表达式为具有特定 SQL 数据库类型,但不会在数据库端渲染 CAST 关键字或数据类型。当处理JSON
数据类型时,type_coerce()
特别重要,它通常与不同平台上的字符串定向数据类型有着错综复杂的关系,甚至可能不是一个显式的数据类型,例如在 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"'
)被渲染。
选择(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()
方法执行它;使用这种方法,我们仍然从结果中获取 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 构造。
SqlAlchemy 2.0 中文文档(二)(3)https://developer.aliyun.com/article/1560325