SqlAlchemy 2.0 中文文档(五十五)(4)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: SqlAlchemy 2.0 中文文档(五十五)

SqlAlchemy 2.0 中文文档(五十五)(3)https://developer.aliyun.com/article/1563189


SQL 表达语言

对象不会生成缓存键,性能影响

SQLAlchemy 从版本 1.4 开始包含一个 SQL 编译缓存设施,它允许 Core 和 ORM SQL  构造缓存它们的字符串形式,以及用于从语句中获取结果的其他结构信息,当下次使用另一个结构上等效的构造时,可以跳过相对昂贵的字符串编译过程。该系统依赖于为所有  SQL 构造实现的功能,包括对象如Columnselect()TypeEngine对象,以生成完全代表它们状态的缓存键,以影响 SQL 编译过程。

如果问题中的警告涉及到广泛使用的对象,如Column对象,并且显示影响到发出的大多数 SQL 构造(使用估算缓存性能使用日志记录描述的估算技术),以至于缓存通常不会为应用程序启用,这将对性能产生负面影响,并且在某些情况下,与之前的 SQLAlchemy 版本相比,实际上会产生性能下降。在为什么升级到 1.4 和/或 2.x 后我的应用程序变慢?的常见问题解答中详细介绍了这一点。

如果有任何疑问,缓存会自行禁用。

缓存依赖于能够以一致的方式生成准确代表语句完整结构的缓存键。如果特定的 SQL 构造(或类型)没有适当的指令,允许它生成正确的缓存键,那么不能安全地启用缓存:

  • 缓存键必须代表完整结构:如果使用两个不同实例的构造可能导致渲染不同 SQL,那么针对第一个元素使用不捕捉第一和第二元素之间不同之处的缓存键缓存 SQL,将导致第二个实例渲染出错误的 SQL。
  • 缓存键必须是一致的:如果一个构造表示每次都会改变的状态,比如字面值,为每个实例生成唯一的 SQL,那么这个构造也不安全可缓存,因为重复使用构造将快速填满语句缓存,其中包含可能不会再次使用的唯一 SQL 字符串,从而破坏了缓存的目的。

由于上述两个原因,SQLAlchemy 的缓存系统对于决定是否缓存与对象对应的 SQL 是非常保守的。

缓存断言属性

根据以下标准发出警告。有关每个标准的详细信息,请参见 为什么升级到 1.4 和/或 2.x 后我的应用程序变慢了?。

  • Dialect 本身(即由我们传递给 create_engine() 的 URL 的第一部分指定的模块,如 postgresql+psycopg2://),必须指示已经审查和测试以正确支持缓存,这由 Dialect.supports_statement_cache 属性设置为 True 来表示。在使用第三方方言时,请咨询方言的维护人员,以便他们遵循确保可以启用缓存的步骤并发布新版本。
  • 第三方或用户定义的类型,它们继承自TypeDecoratorUserDefinedType,必须在其定义中包含 ExternalType.cache_ok 属性,包括所有派生子类,遵循ExternalType.cache_ok的文档字符串中描述的准则。与以前一样,如果这些数据类型是从第三方库导入的,请咨询该库的维护人员,以便他们提供必要的更改并发布新版本。
  • 第三方或用户定义的 SQL 构造,从类似 ClauseElementColumnInsert 等类继承,包括简单的子类以及设计用于与自定义 SQL 构造和编译扩展一起工作的那些,通常应包括 HasCacheKey.inherit_cache 属性设置为 TrueFalse,根据构造的设计,遵循在为自定义构造启用缓存支持中描述的准则。

另请参阅

使用日志估算缓存性能 - 关于观察缓存行为和效率的背景知识

为什么升级到 1.4 和/或 2.x 后我的应用程序变慢了? - 在常见问题解答部分 ### Compiler StrSQLCompiler 无法渲染类型为 的元素

这个错误通常发生在尝试将包含不属于默认编译的元素的 SQL 表达式构造转换为字符串时;在这种情况下,错误将针对 StrSQLCompiler 类。在较少见的情况下,当使用错误类型的 SQL 表达式与特定类型的数据库后端一起使用时,也会发生这种情况;在这些情况下,将命名其他类型的 SQL 编译器类,如 SQLCompilersqlalchemy.dialects.postgresql.PGCompiler。以下指导更具体地针对“字符串化”用例,但也描述了一般背景。

通常,核心 SQL 构造或 ORM Query 对象可以直接转换为字符串,例如当我们使用 print() 时:

>>> from sqlalchemy import column
>>> print(column("x") == 5)
x  =  :x_1 

当上述 SQL 表达式被转换为字符串时,将使用 StrSQLCompiler 编译器类,这是一个特殊的语句编译器,当一个构造被转换为字符串时,没有任何特定于方言的信息时会被调用。

然而,有许多构造是特定于某种数据库方言的,对于这些构造,StrSQLCompiler 不知道如何转换为字符串,例如 PostgreSQL 的 “insert on conflict” 构造:

>>> from sqlalchemy.dialects.postgresql import insert
>>> from sqlalchemy import table, column
>>> my_table = table("my_table", column("x"), column("y"))
>>> insert_stmt = insert(my_table).values(x="foo")
>>> insert_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["y"])
>>> print(insert_stmt)
Traceback (most recent call last):
...
sqlalchemy.exc.UnsupportedCompilationError:
Compiler <sqlalchemy.sql.compiler.StrSQLCompiler object at 0x7f04fc17e320>
can't render element of type
<class 'sqlalchemy.dialects.postgresql.dml.OnConflictDoNothing'>

为了将特定于特定后端的结构字符串化,必须使用ClauseElement.compile()方法,传递一个Engine或一个Dialect对象,该对象将调用正确的编译器。下面我们使用了一个 PostgreSQL 方言:

>>> from sqlalchemy.dialects import postgresql
>>> print(insert_stmt.compile(dialect=postgresql.dialect()))
INSERT  INTO  my_table  (x)  VALUES  (%(x)s)  ON  CONFLICT  (y)  DO  NOTHING 

对于 ORM Query 对象,可以使用 Query.statement 访问器访问语句:

statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))

请查看下方的常见问题解答链接,了解关于直接字符串化/编译 SQL 元素的额外细节。

请参阅

如何将 SQL 表达式呈现为字符串,可能会内联绑定参数?

TypeError: 不支持‘ColumnProperty’和实例之间的操作

当尝试在 SQL 表达式的上下文中使用 column_property()deferred() 对象时,通常会发生这种情况,通常是在声明性的上下文中,如下所示:

class Bar(Base):
    __tablename__ = "bar"
    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))
    __table_args__ = (CheckConstraint(cprop > 5),)

上面,cprop 属性在映射之前内联使用,但是这个 cprop 属性不是一个Column,它是一个ColumnProperty,这是一个临时对象,因此它没有 Column 对象或 InstrumentedAttribute 对象的全部功能,一旦声明过程完成,它将被映射到 Bar 类上。

虽然 ColumnProperty 有一个 __clause_element__() 方法,它允许它在某些面向列的上下文中工作,但它不能在上面示例中所示的开放式比较上下文中工作,因为它没有 Python __eq__() 方法,该方法允许它将与数字“5”的比较解释为 SQL 表达式而不是常规 Python 比较。

解决方案是直接使用Column访问ColumnProperty.expression属性:

class Bar(Base):
    __tablename__ = "bar"
    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))
    __table_args__ = (CheckConstraint(cprop.expression > 5),)

绑定参数(在参数组中)需要一个值

当语句隐式或显式地使用bindparam(),并且在执行语句时没有提供值时,会发生此错误:

stmt = select(table.c.column).where(table.c.id == bindparam("my_param"))
result = conn.execute(stmt)

在上述示例中,没有为参数“my_param”提供值。正确的方法是提供一个值:

result = conn.execute(stmt, {"my_param": 12})

当消息采用“在参数组< y >中需要为绑定参数< x  >提供值”的形式时,消息是指执行的“executemany”风格。在这种情况下,语句通常是 INSERT、UPDATE 或  DELETE,并且正在传递参数列表。在这种格式中,语句可以动态生成,以包含参数列表中提供的每个参数的参数位置,它将使用第一组参数来确定这些参数应该是什么。

例如,下面的语句是基于第一个参数集计算的,需要参数“a”、“b”和“c” - 这些名称确定了语句的最终字符串格式,该格式将用于列表中的每个参数集。由于第二个条目不包含“b”,因此会生成此错误:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError)
A value is required for bind parameter 'b', in parameter group 1
[SQL: u'INSERT INTO t (a, b, c) VALUES (?, ?, ?)']
[parameters: [{'a': 1, 'c': 3, 'b': 2}, {'a': 2, 'c': 4}, {'a': 3, 'c': 5, 'b': 4}]]

由于“b”是必需的,因此将其传递为None,以便进行 INSERT 操作:

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "b": None, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)

请参阅

发送参数 ### 期望的 FROM 子句,得到 Select。要创建 FROM 子句,请使用 .subquery() 方法

这是 SQLAlchemy 1.4 所做的更改,其中通过诸如select()之类的函数生成的 SELECT 语句,但也包括联合和文本 SELECT 表达式等内容不再被视为FromClause对象,并且不能直接放置在另一个 SELECT 语句的 FROM 子句中,而不是首先将它们包装在Subquery中。这是核心中的一个重大概念性变化,完整的原理在 SELECT 语句不再隐式视为 FROM 子句中讨论。

给出一个示例:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
stmt = select(t)

在上述示例中,stmt表示一个 SELECT 语句。当我们想要直接将stmt作为另一个 SELECT 语句的 FROM 子句时,比如如果我们试图从中选择:

new_stmt_1 = select(stmt)

或者如果我们想在 FROM 子句中使用它,比如在 JOIN 中:

new_stmt_2 = select(some_table).select_from(some_table.join(stmt))

在 SQLAlchemy 的早期版本中,使用一个 SELECT 语句在另一个 SELECT  语句内会产生一个有括号的无名称子查询。在大多数情况下,这种形式的 SQL 不是很有用,因为像 MySQL 和 PostgreSQL  这样的数据库要求 FROM 子句中的子查询具有命名别名,这意味着需要使用SelectBase.alias()方法或者从 1.4 版本开始使用SelectBase.subquery()方法来产生这个。在其他数据库中,子查询有一个名称来解析子查询内部列名的任何歧义仍然更清晰。

除了上述实际原因外,还有很多其他与 SQLAlchemy 相关的原因导致进行此更改。因此,上述两个语句的正确形式要求使用SelectBase.subquery()

subq = stmt.subquery()
new_stmt_1 = select(subq)
new_stmt_2 = select(some_table).select_from(some_table.join(subq))

另请参阅

SELECT 语句不再隐式地被视为 FROM 子句 ### 为原始 clauseelement 自动生成别名

版本 1.4.26 中新增。

这个弃用警告指的是一个非常古老且可能不太为人所知的模式,适用于传统的Query.join()方法以及 2.0 风格的Select.join()方法,其中联接可以根据relationship()来表示,但目标是将类映射到的Table或其他核心可选择项,而不是 ORM 实体,比如映射类或aliased()构造:

a1 = Address.__table__
q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
    .all()
)

上述模式还允许任意可选择项,比如核心JoinAlias对象,但是没有对此元素的自动适应,这意味着需要直接引用核心元素:

a1 = Address.__table__.alias()
q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "ed@foo.com")
    .all()
)

指定联接目标的正确方法始终是使用映射类本身或一个aliased对象,后者使用PropComparator.of_type()修饰符来设置一个别名:

# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "ed@foo.com")
# name Address target explicitly, not necessary but legal
q = (
    s.query(User)
    .join(Address, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
)

加入到别名:

from sqlalchemy.orm import aliased
a1 = aliased(Address)
# of_type() form; recommended
q = (
    s.query(User)
    .join(User.addresses.of_type(a1))
    .filter(a1.email_address == "ed@foo.com")
)
# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "ed@foo.com")
```### 由于重叠表而自动生成别名
新版本为 1.4.26。
此警告通常是在使用`Select.join()`方法或传统的`Query.join()`方法进行查询时生成的,其中涉及到涉及连接表继承的映射。问题在于,在两个共享共同基表的连接继承模型之间进行连接时,如果不对其中一个或另一个应用别名,就无法形成两个实体之间的适当 SQL JOIN;SQLAlchemy 将别名应用于连接的右侧。例如,考虑到连接继承映射:
```py
class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    manager_id = Column(ForeignKey("manager.id"))
    name = Column(String(50))
    type = Column(String(50))
    reports_to = relationship("Manager", foreign_keys=manager_id)
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }
class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "inherit_condition": id == Employee.id,
    }

上述映射包括EmployeeManager类之间的关系。由于两个类都使用“employee”数据库表,从 SQL 的角度来看,这是一种自引用关系。如果我们想要使用连接从EmployeeManager模型中查询,SQL 级别上“employee”表需要在查询中包含两次,这意味着它必须被别名化。当我们使用 SQLAlchemy ORM 创建这样的连接时,我们得到的 SQL 看起来像下面这样:

>>> stmt = select(Employee, Manager).join(Employee.reports_to)
>>> print(stmt)
SELECT  employee.id,  employee.manager_id,  employee.name,
employee.type,  manager_1.id  AS  id_1,  employee_1.id  AS  id_2,
employee_1.manager_id  AS  manager_id_1,  employee_1.name  AS  name_1,
employee_1.type  AS  type_1
FROM  employee  JOIN
(employee  AS  employee_1  JOIN  manager  AS  manager_1  ON  manager_1.id  =  employee_1.id)
ON  manager_1.id  =  employee.manager_id 

上面的 SQL 从employee表中选择,表示查询中的Employee实体。然后加入到employee AS employee_1 JOIN manager AS manager_1的右嵌套连接,其中再次声明了employee表,但作为匿名别名employee_1。这就是警告消息所指的“自动生成别名”。

当 SQLAlchemy 加载包含EmployeeManager对象的 ORM 行时,ORM 必须将来自上述employee_1manager_1表别名的行适应为未别名化的Manager类的行。这个过程在内部是复杂的,并且不支持所有 API 功能,特别是当尝试在比这里展示的更深度嵌套查询中使用急加载功能时,如contains_eager()。由于该模式对于更复杂的情况不可靠,并涉及难以预测和遵循的隐式决策,因此会发出警告,并且该模式可能被视为传统功能。编写此查询的更好方法是使用适用于任何其他自引用关系的相同模式,即显式使用aliased()构造。对于联接继承和其他基于联接的映射,通常希望添加使用aliased.flat参数,这将允许通过将别名应用于联接中的各个表来对两个或更多表进行联接别名化,而不是将联接嵌入到新的子查询中:

>>> from sqlalchemy.orm import aliased
>>> manager_alias = aliased(Manager, flat=True)
>>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias))
>>> print(stmt)
SELECT  employee.id,  employee.manager_id,  employee.name,
employee.type,  manager_1.id  AS  id_1,  employee_1.id  AS  id_2,
employee_1.manager_id  AS  manager_id_1,  employee_1.name  AS  name_1,
employee_1.type  AS  type_1
FROM  employee  JOIN
(employee  AS  employee_1  JOIN  manager  AS  manager_1  ON  manager_1.id  =  employee_1.id)
ON  manager_1.id  =  employee.manager_id 

如果我们想要使用contains_eager()来填充reports_to属性,我们将引用别名:

>>> stmt = (
...     select(Employee)
...     .join(Employee.reports_to.of_type(manager_alias))
...     .options(contains_eager(Employee.reports_to.of_type(manager_alias)))
... )

在某些更嵌套的情况下,如果没有使用显式的aliased()对象,在 ORM 在非常嵌套的上下文中“自动别名化”的情况下,contains_eager()选项可能没有足够的上下文来知道从哪里获取其数据。因此,最好不要依赖此功能,而是尽可能保持 SQL 构造尽可能明确。###对象不会生成缓存键,性能影响

截至版本 1.4,SQLAlchemy 包括一个 SQL 编译缓存设施,它允许 Core 和 ORM SQL  构造缓存它们的字符串形式,以及用于从语句中获取结果的其他结构信息,这样当下次使用另一个结构等效的构造时,就可以跳过相对昂贵的字符串编译过程。该系统依赖于为所有  SQL 构造实现的功能,包括诸如Columnselect()TypeEngine对象等对象,以生成完全代表它们状态的缓存键,以至于影响 SQL 编译过程。

如果警告涉及到广泛使用的对象,比如Column对象,并且显示为影响到大部分发出的 SQL 构造(使用通过日志估算缓存性能描述的估算技术)以至于缓存通常不会为应用程序启用,这将对性能产生负面影响,并且在某些情况下,与之前的 SQLAlchemy 版本相比实际上会产生性能下降。在为什么升级到 1.4 和/或 2.x 后我的应用程序变慢了?的常见问题解答中详细介绍了这一点。

如果存在任何疑问,缓存会自行禁用

缓存依赖于能够生成一个缓存键,以一种一致的方式准确地表示语句的完整结构。如果某个特定的 SQL 构造(或类型)没有适当的指令来生成正确的缓存键,那么就不能安全地启用缓存:

  • 缓存键必须表示完整的结构:如果两个单独实例的使用可能导致呈现不同 SQL,则针对第一个元素使用一个不捕获第一和第二个元素之间不同之处的缓存键缓存 SQL,将导致为第二个实例缓存并呈现不正确的 SQL。
  • 缓存键必须是一致的:如果一个构造代表每次都会更改的状态,比如文字值,为每个实例产生唯一的 SQL,那么这个构造也不安全可以缓存,因为重复使用这个构造将很快填满语句缓存,里面包含的唯一 SQL 字符串可能不会再次使用,从而使缓存失去了意义。

由于上述两个原因,SQLAlchemy 的缓存系统在决定是否缓存与对象对应的 SQL 时极其保守

缓存的断言属性

根据以下标准发出警告。有关每个标准的更多详细信息,请参阅升级到 1.4 和/或 2.x 后为什么我的应用程序变慢?部分。

  • Dialect本身(即由我们传递给create_engine()的 URL 的第一部分指定的模块,如postgresql+psycopg2://),必须指示已经审查并测试以正确支持缓存,这由Dialect.supports_statement_cache属性设置为True来指示。在使用第三方方言时,请与方言的维护者协商,以便他们遵循确保可以启用缓存的步骤并发布新版本。
  • 第三方或用户定义的类型,继承自TypeDecoratorUserDefinedType,必须在其定义中包含ExternalType.cache_ok属性,包括所有派生子类,在ExternalType.cache_ok的文档字符串中描述的指南中遵循。同样,如果这些数据类型是从第三方库导入的,请与该库的维护者协商,以便他们提供必要的更改并发布新版本。
  • 第三方或用户定义的 SQL 构造,从诸如ClauseElementColumnInsert等类继承,包括简单的子类以及设计用于与自定义 SQL 构造和编译扩展一起工作的子类,通常应包含HasCacheKey.inherit_cache属性设置为TrueFalse,根据构造的设计,在为自定义构造启用缓存支持中描述的指南。

另请参阅

使用日志估算缓存性能 - 观察缓存行为和效率的背景

为什么升级到 1.4 和/或 2.x 后我的应用程序变慢了? - 在常见问题部分

如果有任何疑问,缓存会自行禁用

缓存依赖于能够生成准确代表语句的完整结构的缓存键,以一致的方式。如果特定的 SQL 结构(或类型)没有适当的指令来允许其生成正确的缓存键,则不能安全地启用缓存:

  • 缓存键必须代表完整结构:如果使用两个不同实例的结构可能导致渲染不同的 SQL,则使用不捕获第一个和第二个元素之间不同之处的缓存键对第一个元素的 SQL 进行缓存将导致第二个实例的 SQL 被错误地缓存和渲染。
  • 缓存键必须是一致的:如果一个结构代表每次都会改变的状态,比如字面值,为每个实例生成唯一的 SQL,那么这个结构也不能安全地缓存,因为对该结构的重复使用将迅速用唯一的 SQL 字符串填满语句缓存,这些字符串可能不会再次使用,从而打破缓存的目的。

由于上述两个原因,SQLAlchemy 的缓存系统对于决定是否缓存与对象对应的 SQL 是极端保守的

缓存的断言属性

根据以下标准发出警告。有关每个标准的更多详细信息,请参见 为什么升级到 1.4 和/或 2.x 后我的应用程序变慢了?一节。

  • Dialect 本身(即我们传递给 create_engine() 的 URL 的第一部分指定的模块,如 postgresql+psycopg2://),必须指示它已经经过审查和测试,以正确支持缓存,这由 Dialect.supports_statement_cache 属性设置为 True 来表示。使用第三方方言时,请咨询方言的维护者,以便他们可以按照确保可以启用缓存的步骤进行操作,并发布一个新的版本。
  • TypeDecoratorUserDefinedType 继承的第三方或用户定义的类型必须在其定义中包含 ExternalType.cache_ok 属性,包括所有派生子类,在外部类型缓存支持 的文档字符串中描述的准则。与以前一样,如果这些数据类型是从第三方库导入的,请咨询该库的维护者,以便他们提供必要的更改并发布新版本。
  • 第三方或用户定义的 SQL 构造,它们从类中子类化,如ClauseElementColumnInsert 等,包括简单的子类以及那些设计用于与 自定义 SQL 构造和编译扩展一起工作的子类,通常应该将HasCacheKey.inherit_cache 属性设置为 TrueFalse,根据构造的设计,遵循在 启用自定义构造的缓存支持 中描述的准则。

参见

使用日志估算缓存性能 - 观察缓存行为和效率的背景知识

为什么我的应用程序在升级到 1.4 和/或 2.x 后变慢? - 在常见问题解答部分

编译器 StrSQLCompiler 无法渲染类型为 的元素

当尝试将包含不属于默认编译的元素的 SQL 表达式构造进行字符串化时,通常会发生此错误;在这种情况下,错误将针对StrSQLCompiler 类。在较少见的情况下,当使用错误类型的 SQL 表达式与特定类型的数据库后端时,也可能发生这种情况;在这些情况下,将命名其他类型的 SQL 编译器类,例如 SQLCompilersqlalchemy.dialects.postgresql.PGCompiler。下面的指导更具体地针对“字符串化”用例,但也描述了一般背景。

通常,Core SQL 构造或 ORM Query对象可以直接转换为字符串,比如当我们使用print()时:

>>> from sqlalchemy import column
>>> print(column("x") == 5)
x  =  :x_1 

当上述 SQL 表达式被字符串化时,将使用StrSQLCompiler 编译器类,这是一个特殊的语句编译器,当一个构造在没有任何特定于方言的信息的情况下被字符串化时会被调用。

然而,有许多构造是特定于某种数据库方言的,对于这些构造,StrSQLCompiler 不知道如何转换为字符串,比如 PostgreSQL 的“insert on conflict”构造:

>>> from sqlalchemy.dialects.postgresql import insert
>>> from sqlalchemy import table, column
>>> my_table = table("my_table", column("x"), column("y"))
>>> insert_stmt = insert(my_table).values(x="foo")
>>> insert_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["y"])
>>> print(insert_stmt)
Traceback (most recent call last):
...
sqlalchemy.exc.UnsupportedCompilationError:
Compiler <sqlalchemy.sql.compiler.StrSQLCompiler object at 0x7f04fc17e320>
can't render element of type
<class 'sqlalchemy.dialects.postgresql.dml.OnConflictDoNothing'>

为了将特定于特定后端的构造转换为字符串,必须使用ClauseElement.compile() 方法,传递一个 Engine 或一个 Dialect 对象,这将调用正确的编译器。下面我们使用一个 PostgreSQL 方言:

>>> from sqlalchemy.dialects import postgresql
>>> print(insert_stmt.compile(dialect=postgresql.dialect()))
INSERT  INTO  my_table  (x)  VALUES  (%(x)s)  ON  CONFLICT  (y)  DO  NOTHING 

对于 ORM Query 对象,可以通过Query.statement访问器访问语句:

statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))

请查看下面的 FAQ 链接,了解有关 SQL 元素的直接字符串化/编译的更多详细信息。

另请参阅

如何将 SQL 表达式呈现为字符串,可能包含内联的绑定参数?

TypeError: not supported between instances of ‘ColumnProperty’ and

当尝试在 SQL 表达式的上下文中使用column_property()deferred()对象时,通常在声明中会出现这种情况:

class Bar(Base):
    __tablename__ = "bar"
    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))
    __table_args__ = (CheckConstraint(cprop > 5),)

在上面的例子中,在映射之前内联使用了cprop属性,但是这个cprop属性不是一个Column,它是一个ColumnProperty,这是一个临时对象,因此不具备Column对象或InstrumentedAttribute对象的全部功能,一旦声明过程完成,它将映射到Bar类上。

虽然ColumnProperty确实具有__clause_element__()方法,允许它在某些面向列的上下文中工作,但是它无法在开放式比较上下文中工作,如上所示,因为它没有 Python __eq__() 方法,该方法将允许它将对数字“5”的比较解释为 SQL 表达式而不是常规的 Python 比较。

解决方案是直接访问Column,使用ColumnProperty.expression 属性:

class Bar(Base):
    __tablename__ = "bar"
    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))
    __table_args__ = (CheckConstraint(cprop.expression > 5),)

绑定参数(在参数组中)需要值

当语句在执行时使用bindparam() 时,如果未显式或隐式地提供值,则会出现此错误:

stmt = select(table.c.column).where(table.c.id == bindparam("my_param"))
result = conn.execute(stmt)

上述情况下,未为参数 “my_param” 提供任何值。正确的方法是提供一个值:

result = conn.execute(stmt, {"my_param": 12})

当消息采用“在参数组中需要绑定参数的值”的形式时,消息是指“executemany”执行风格。在这种情况下,语句通常是  INSERT、UPDATE 或  DELETE,并且正在传递参数列表。在此格式中,语句可以动态生成,以包括参数列表中提供的每个参数的参数位置,其中它将使用第一组参数来确定这些参数应该是什么。

例如,以下语句是基于第一个参数集计算的,要求参数 “a”、“b” 和 “c” - 这些名称确定语句的最终字符串格式,该格式将用于列表中每个参数集的参数。由于第二个条目不包含 “b”,因此会生成此错误:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError)
A value is required for bind parameter 'b', in parameter group 1
[SQL: u'INSERT INTO t (a, b, c) VALUES (?, ?, ?)']
[parameters: [{'a': 1, 'c': 3, 'b': 2}, {'a': 2, 'c': 4}, {'a': 3, 'c': 5, 'b': 4}]]

由于“b”是必需的,因此将其传递为 None,以便 INSERT 可以继续进行:

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "b": None, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)

另请参阅

发送参数

期望 FROM 子句,但是得到了 Select。要创建 FROM 子句,请使用 .subquery() 方法

这指的是 SQLAlchemy 1.4 中的一项更改,根据此更改,由 select() 等函数生成的 SELECT 语句,但也包括联合和文本型 SELECT 表达式,不再被视为 FromClause 对象,而且不能直接放在另一个 SELECT 语句的 FROM 子句中,而必须首先将它们包装在 Subquery 中。这是 Core 中的一个重大概念变更,完整的解释在 不再将 SELECT 语句隐式视为 FROM 子句 中讨论。

举个例子:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
stmt = select(t)

在上述中,stmt 表示一个 SELECT 语句。当我们想要直接将 stmt 用作另一个 SELECT 的 FROM 子句时,比如我们试图从中选择时,会产生错误:

new_stmt_1 = select(stmt)

或者,如果我们想在 FROM 子句中使用它,比如在 JOIN 中:

new_stmt_2 = select(some_table).select_from(some_table.join(stmt))

在之前的 SQLAlchemy 版本中,使用一个 SELECT 嵌套在另一个 SELECT  中会产生一个带括号的、未命名的子查询。在大多数情况下,这种 SQL 形式并不是很有用,因为像 MySQL 和 PostgreSQL  这样的数据库要求 FROM 子句中的子查询具有命名别名,这意味着需要使用 SelectBase.alias() 方法,或者从 1.4 开始使用 SelectBase.subquery() 方法来实现这一点。在其他数据库中,为子查询命名仍然更清晰,以解决在子查询内部对列名的未来引用可能产生的任何歧义。

除了上述实际原因外,还有许多其他基于 SQLAlchemy 的原因导致了这一更改的进行。因此,上述两个语句的正确形式要求使用 SelectBase.subquery()

subq = stmt.subquery()
new_stmt_1 = select(subq)
new_stmt_2 = select(some_table).select_from(some_table.join(subq))

另请参阅

不再将 SELECT 语句隐式视为 FROM 子句

自动为原始 clauseelement 生成别名

自 1.4.26 版开始新加入的功能。

此弃用警告是针对非常古老且可能不为人知的模式的,该模式适用于遗留的Query.join()方法以及 2.0 样式 Select.join()方法,其中可以根据relationship()指定连接,但目标是Table或其他映射到类的 Core 可选择对象,而不是 ORM 实体,如映射类或aliased()构造:

a1 = Address.__table__
q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
    .all()
)

以上模式还允许任意可选择对象,例如 Core JoinAlias对象,但是没有此元素的自动适应,这意味着必须直接引用 Core 元素:

a1 = Address.__table__.alias()
q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "ed@foo.com")
    .all()
)

指定连接目标的正确方式始终是使用映射类本身或一个aliased对象,后者使用PropComparator.of_type()修饰符来设置别名:

# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "ed@foo.com")
# name Address target explicitly, not necessary but legal
q = (
    s.query(User)
    .join(Address, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
)

连接到别名:

from sqlalchemy.orm import aliased
a1 = aliased(Address)
# of_type() form; recommended
q = (
    s.query(User)
    .join(User.addresses.of_type(a1))
    .filter(a1.email_address == "ed@foo.com")
)
# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "ed@foo.com")

由于表重叠而自动生成别名

自 1.4.26 版新增。

当使用Select.join()方法或遗留的Query.join()方法查询涉及联合表继承的映射时,通常会生成此警告。问题在于,当在两个共享公共基表的联合继承模型之间进行连接时,如果不对其中一侧应用别名,则无法形成两个实体之间的适当  SQL JOIN;SQLAlchemy 对连接的右侧应用了别名。例如,给定一个联合继承映射:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    manager_id = Column(ForeignKey("manager.id"))
    name = Column(String(50))
    type = Column(String(50))
    reports_to = relationship("Manager", foreign_keys=manager_id)
    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }
class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "inherit_condition": id == Employee.id,
    }

以上映射包括EmployeeManager类之间的关系。由于这两个类都使用“employee”数据库表,从 SQL 角度来看,这是一个自引用关系。如果我们想要使用连接从EmployeeManager模型查询,那么在 SQL 级别上,“employee”表需要在查询中出现两次,这意味着必须给它起个别名。当我们使用 SQLAlchemy ORM 创建这样的连接时,得到的 SQL 如下所示:

>>> stmt = select(Employee, Manager).join(Employee.reports_to)
>>> print(stmt)
SELECT  employee.id,  employee.manager_id,  employee.name,
employee.type,  manager_1.id  AS  id_1,  employee_1.id  AS  id_2,
employee_1.manager_id  AS  manager_id_1,  employee_1.name  AS  name_1,
employee_1.type  AS  type_1
FROM  employee  JOIN
(employee  AS  employee_1  JOIN  manager  AS  manager_1  ON  manager_1.id  =  employee_1.id)
ON  manager_1.id  =  employee.manager_id 

在上面,SQL 从 employee 表中选择,表示查询中的 Employee 实体。然后加入到一个右嵌套连接 employee AS employee_1 JOIN manager AS manager_1,其中 employee 表再次出现,但是作为一个匿名别名 employee_1。这就是警告消息所指的‘自动生成别名’。

当 SQLAlchemy 加载包含一个 Employee 和一个 Manager 对象的 ORM 行时,ORM 必须将来自上面的 employee_1manager_1 表别名的行适配到未别名化的 Manager 类中。这个过程内部复杂,并且不能适应所有 API 特性,尤其是当尝试使用比这里显示的更深度嵌套的查询时,如 contains_eager() 等急切加载特性。由于该模式对于更复杂的场景不可靠,并涉及难以预测和遵循的隐式决策,因此会发出警告,并且该模式可能被视为一种传统特性。编写此查询的更好方法是使用适用于任何其他自引用关系的相同模式,即显式使用 aliased() 构造。对于连接继承和其他基于连接的映射,通常希望添加使用 aliased.flat 参数的使用,这将允许通过将别名应用于连接中的各个表来对两个或多个表进行 JOIN,而不是将连接嵌入到新的子查询中:

>>> from sqlalchemy.orm import aliased
>>> manager_alias = aliased(Manager, flat=True)
>>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias))
>>> print(stmt)
SELECT  employee.id,  employee.manager_id,  employee.name,
employee.type,  manager_1.id  AS  id_1,  employee_1.id  AS  id_2,
employee_1.manager_id  AS  manager_id_1,  employee_1.name  AS  name_1,
employee_1.type  AS  type_1
FROM  employee  JOIN
(employee  AS  employee_1  JOIN  manager  AS  manager_1  ON  manager_1.id  =  employee_1.id)
ON  manager_1.id  =  employee.manager_id 

如果我们想要使用 contains_eager() 来填充 reports_to 属性,我们引用别名:

>>> stmt = (
...     select(Employee)
...     .join(Employee.reports_to.of_type(manager_alias))
...     .options(contains_eager(Employee.reports_to.of_type(manager_alias)))
... )

在某些更嵌套的情况下,如果 ORM 在非常嵌套的上下文中“自动别名”,则不使用显式 aliased() 对象,contains_eager() 选项没有足够的上下文来知道从哪里获取其数据。因此,最好不要依赖此功能,而是尽可能保持 SQL 构造的显式性。


SqlAlchemy 2.0 中文文档(五十五)(5)https://developer.aliyun.com/article/1563192

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
1月前
|
SQL 数据库连接 API
SqlAlchemy 2.0 中文文档(五十五)(3)
SqlAlchemy 2.0 中文文档(五十五)
34 1
|
1月前
|
SQL 安全 数据库连接
SqlAlchemy 2.0 中文文档(五十五)(5)
SqlAlchemy 2.0 中文文档(五十五)
17 1
|
1月前
|
SQL 数据库连接 数据库
SqlAlchemy 2.0 中文文档(五十五)(2)
SqlAlchemy 2.0 中文文档(五十五)
19 1
|
1月前
|
SQL 缓存 数据库连接
SqlAlchemy 2.0 中文文档(五十五)(1)
SqlAlchemy 2.0 中文文档(五十五)
20 1
|
1月前
|
API 数据库 Python
SqlAlchemy 2.0 中文文档(五十五)(6)
SqlAlchemy 2.0 中文文档(五十五)
16 1
|
1月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(五十四)(3)
SqlAlchemy 2.0 中文文档(五十四)
17 1
|
1月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(五十四)(2)
SqlAlchemy 2.0 中文文档(五十四)
29 1
|
1月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(五十四)(4)
SqlAlchemy 2.0 中文文档(五十四)
19 1
|
1月前
|
SQL 关系型数据库 API
SqlAlchemy 2.0 中文文档(五十四)(5)
SqlAlchemy 2.0 中文文档(五十四)
27 1
|
1月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(五十四)(1)
SqlAlchemy 2.0 中文文档(五十四)
9 0